Compare commits

...

27 Commits

Author SHA1 Message Date
f37d42263e driver fix g-wolves 2023-11-22 20:14:09 +01:00
bd42c93fa2 driver g-wolves fix 2023-11-22 20:06:09 +01:00
ee60060dc6 Moved UI from C to Vala
Multiple changes coming with that
Massive cleanup
2023-11-22 11:37:04 +01:00
5ab6aefa49 Added debug target in Makefile 2023-11-07 13:33:39 +01:00
de55b4cdd6 Fixed README 2023-11-07 13:33:26 +01:00
573234295f Added LICENSE 2023-11-07 12:29:56 +01:00
8a557e8226 driver: g-wolves polling rate investigation 2023-10-01 14:43:46 +02:00
40eaff8018 driver: g-wolves changed unk0 to polling rate 2023-10-01 14:23:54 +02:00
c644e75cbd UI wireless state support 2023-10-01 13:21:37 +02:00
ad28b82bb8 wireless driver interface, g-wolves implementation 2023-10-01 13:04:34 +02:00
c11c203e14 UI polling rate changes 2023-10-01 12:51:13 +02:00
aa3567d1e8 Angle snap added to UI and driver support 2023-09-30 23:56:47 +02:00
22cb968b64 Motion sync loaded in UI, support in g-wolves 2023-09-30 23:50:33 +02:00
a12e682eda typo 2023-09-30 23:12:21 +02:00
64dada3448 Mouse UI :) 2023-09-30 23:07:13 +02:00
e5cd168c18 Added driver interface for motionsync/anglesnap 2023-09-30 13:15:46 +02:00
c535c91a48 Visual changes, radiobutton for dpi stages 2023-09-29 15:50:38 +02:00
6ed60bea1c Updated needed adwaita version 2023-09-28 19:05:04 +02:00
8205d7e27a Graphical improvements, fix compile warnings 2023-09-28 18:55:20 +02:00
7c4b41666b graphical dpi show improvements 2023-09-28 18:10:08 +02:00
8c8df678c4 driver: g-wolves y dpi notes 2023-09-28 17:46:47 +02:00
7dc6a89c04 Added mouse DPI interface 2023-09-28 17:08:28 +02:00
69c9b6e342 driver interface updates, included in razer/gwolve 2023-09-28 14:52:17 +02:00
de3167d140 Updated driver interface 2023-09-28 14:43:51 +02:00
84735a86f0 Added DPI stage box on mouse panel 2023-09-28 13:10:31 +02:00
356d0b975d driver: added basic razer driver (viper mini) 2023-09-28 00:04:16 +02:00
34e4828ee7 driver: g-wolves refactor 2023-09-27 23:04:48 +02:00
33 changed files with 1599 additions and 922 deletions

280
LICENSE Normal file
View File

@@ -0,0 +1,280 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS

View File

@@ -1,24 +1,39 @@
NAME=ginput
CC=gcc
CFLAGS=$(shell pkg-config --cflags gtk4 libadwaita-1 libusb-1.0) -O3 -Wall -I src
LDFLAGS=$(shell pkg-config --libs gtk4 libadwaita-1 libusb-1.0)
BUILD_DIR=build
CC=gcc
VALAC=valac
VALA_FLAGS=--pkg libadwaita-1 --gresources ui/ginput.gresource.xml
CFLAGS=$(shell pkg-config --cflags gtk4 libadwaita-1 libusb-1.0 glib-2.0) -O3 -Wall -I src -I $(BUILD_DIR)/vala
LDFLAGS=$(shell pkg-config --libs gtk4 libadwaita-1 libusb-1.0 glib-2.0) -lm
UI := $(shell find ui/ -name '*.ui')
C_FILES := $(shell find src/ -name '*.c')
VALA_FILES := $(shell find src/ -name '*.vala')
VAPI_FILES := $(shell find src/ -name '*.vapi')
VALA_OBJECTS := $(VALA_FILES)
VALA_OBJECTS := $(patsubst %.vala,%.c,$(VALA_OBJECTS))
VALA_OBJECTS := $(VALA_OBJECTS:%=$(BUILD_DIR)/vala/%)
all: resources $(BUILD_DIR)/$(NAME) $(BUILD_DIR)/drivers
# Top-level targets
resources: $(BUILD_DIR)/glib-2.0/schemas/gschemas.compiled
$(BUILD_DIR)/$(NAME): $(BUILD_DIR)/resource.c $(C_FILES) | $(BUILD_DIR)
$(VALA_OBJECTS): $(VALA_FILES) $(VAPI_FILES) | $(BUILD_DIR)/vala
rm -rf $(BUILD_DIR)/vala
$(VALAC) -d $(BUILD_DIR)/vala $(VALA_FLAGS) --header=$(BUILD_DIR)/vala/vala.h -C $^ $(VALA_PKG)
$(BUILD_DIR)/$(NAME): $(BUILD_DIR)/resource.c $(C_FILES) $(VALA_OBJECTS) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Build directory
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(BUILD_DIR)/vala: | $(BUILD_DIR)
mkdir -p $(BUILD_DIR)/vala
# Resources
$(BUILD_DIR)/resource.c: ui/ginput.gresource.xml ui/style.css $(UI)
cp ui/style.css $(BUILD_DIR)/
@@ -42,4 +57,10 @@ clean:
.PHONY: run
.SILENT: run
run: all
XDG_DATA_DIRS=./$(BUILD_DIR) ./$(BUILD_DIR)/ginput
XDG_DATA_DIRS=./$(BUILD_DIR) ./$(BUILD_DIR)/$(NAME)
$(BUILD_DIR)/$(NAME).dbg: $(BUILD_DIR)/resource.c $(C_FILES) $(VALA_OBJECTS) | $(BUILD_DIR)
$(CC) $(CFLAGS) -g -o $@ $^ $(LDFLAGS)
debug: $(BUILD_DIR)/$(NAME).dbg $(BUILD_DIR)/drivers resources
XDG_DATA_DIRS=./$(BUILD_DIR) gdb --args ./$(BUILD_DIR)/$(NAME).dbg

View File

@@ -10,11 +10,58 @@ Drivers **must** expose the following symbols:
```c
void driver_init(); // Initialization
uint32_t driver_getkey(); // Calls to this function must return USB keys that this driver registers, until 0
device_type_t driver_get_type(); // Returns device type
device_capacity_t driver_get_capacity(void* handle); // Returns device capacity (see below)
char* driver_get_name(void* handle); // Returns peripheral name
char* driver_get_image(void* handle); // Returns peripheral image
char* driver_get_manufacturer(void* handle); // Returns manufacturer name
/*
* Mouse drivers
*
* - DPI Levels: max levels, level info (dpi + led), level count, current level
* - Motion sync
* - Angle snap
* - Polling rate : possible values, current value
* - Debounce time : possible values, current value
* - Lift off distance : possible values, current value
* - LED (color/effect/status)
*/
struct MOUSE_DPI_LEVELS
{
unsigned int max_level_count;
unsigned int level_count;
unsigned int level_current;
bool led_available;
bool xy_available;
struct MOUSE_DPI_LEVEL
{
unsigned int dpi_x;
unsigned int dpi_y;
unsigned char r;
unsigned char g;
unsigned char b;
} level[];
};
int driver_mouse_dpi_get(void* handle, struct MOUSE_DPI_LEVELS* output);
int driver_mouse_motion_sync_get(void* handle, bool* output);
int driver_mouse_angle_snap_get(void* handle, bool* output);
struct MOUSE_POLLING_RATES
{
unsigned int polling_rate_count;
unsigned int polling_rate_current;
unsigned int polling_rate_values[];
};
int driver_mouse_polling_rate_get(void* handle, struct MOUSE_POLLING_RATES* output);
/*
* Wireless driver
* - Battery (state + level)
* - Connection type (wired/wireless)
*/
int driver_wireless_battery_state_get(void* handle, int* battery_level, bool* charging);
int driver_wireless_connection_type_get(void* handle, bool* output);
// Mouse drivers
```

View File

@@ -2,7 +2,6 @@ CC=gcc
CFLAGS=$(shell pkg-config --cflags libusb-1.0) -O3 -Wall -shared
LDFLAGS=$(shell pkg-config --libs libusb-1.0)
ASSETS=$(shell find devices/ -name '*.png')
FINAL_ASSETS=$(BUILD_DIR)/drivers/assets/gwolves-htx.png $(BUILD_DIR)/drivers/assets/gwolves-hts_plus.png $(BUILD_DIR)/drivers/assets/gwolves-hsk_pro.png
BUILD_DIR=../../build/

View File

@@ -1,5 +1,7 @@
#include "g-wolves.h"
#define DEVICE_WIRELESS(handle) (is_connection_wireless(handle))
static int send_command(libusb_device_handle* hand, uint8_t command, uint8_t size, void* report, bool wireless)
{
report_header_t* head = report;
@@ -74,6 +76,34 @@ uint32_t driver_getkey(void)
return (VENDOR_ID << 16) | id;
}
device_capacity_t driver_get_capacity(void* handle)
{
libusb_device* dev = handle;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(dev, &desc);
device_capacity_t wireless = 0;
switch(desc.idProduct)
{
case HTX_4K_PRODUCT_ID_WIRED:
case HTX_4K_PRODUCT_ID_WIRELESS:
case HTS_PLUS_4K_PRODUCT_ID_WIRED:
case HTS_PLUS_4K_PRODUCT_ID_WIRELESS:
case HSK_PRO_ACE_PRODUCT_ID_WIRED:
case HSK_PRO_ACE_PRODUCT_ID_WIRELESS:
wireless = DEVICE_CAPACITY_WIRELESS;
break;
}
return DEVICE_CAPACITY_MOUSE | wireless;
}
char* driver_get_manufacturer(void* handle)
{
return MANUFACTURER_NAME;
}
char* driver_get_name(void* handle)
{
libusb_device* dev = handle;
@@ -119,3 +149,135 @@ char* driver_get_image(void* handle)
return "";
}
}
static bool is_connection_wireless(void* handle)
{
libusb_device* dev = handle;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(dev, &desc);
switch(desc.idProduct)
{
case HTX_4K_PRODUCT_ID_WIRELESS:
case HTS_PLUS_4K_PRODUCT_ID_WIRELESS:
case HSK_PRO_ACE_PRODUCT_ID_WIRELESS:
return true;
}
return false;
}
int driver_mouse_dpi_get(void* handle, libusb_device_handle* hand, struct MOUSE* output)
{
// Send command
struct REPORT_DPI_SETTINGS report = {0};
int res = send_command(hand, COMMAND_DPI_SETTINGS, REPORT_DPI_SETTINGS_SIZE, &report, DEVICE_WIRELESS(handle));
if(res <= 0) return -1;
// Format 'output' packet
output->max_level_count = 5;
output->level_count = report.level_count;
output->level_current = report.level_current - 1;
output->led_available = true;
output->xy_available = false;
for(size_t i = 0; i < report.level_count; i++)
{
output->dpi_x[i] = report.levels[i].dpi_x_high << 8 | report.levels[i].dpi_x_low;
output->dpi_y[i] = report.levels[i].dpi_y_high << 8 | report.levels[i].dpi_y_low;
output->r[i] = report.levels[i].led_r;
output->g[i] = report.levels[i].led_g;
output->b[i] = report.levels[i].led_b;
}
return 0;
}
int driver_mouse_motion_sync_get(void* handle, libusb_device_handle* hand, struct MOUSE* output)
{
// Send command
struct REPORT_MOTION_SYNC report = {0};
int res = send_command(hand, COMMAND_MOTION_SYNC, REPORT_MOTION_SYNC_SIZE, &report, DEVICE_WIRELESS(handle));
if(res <= 0) return -1;
// Format output boolean
output->motion_sync = report.motion_sync;
return 0;
}
int driver_mouse_angle_snap_get(void* handle, libusb_device_handle* hand, struct MOUSE* output)
{
// Send command
struct REPORT_ANGLE_SNAP report = {0};
int res = send_command(hand, COMMAND_ANGLE_SNAP, REPORT_ANGLE_SNAP_SIZE, &report, DEVICE_WIRELESS(handle));
if(res <= 0) return -1;
// Format output boolean
output->angle_snap = report.angle_snap;
return 0;
}
int driver_mouse_polling_rate_get(void* handle, libusb_device_handle* hand, struct MOUSE* output)
{
output->polling_rate_count = 0;
return 0;
}
int driver_mouse_get(void* handle, struct MOUSE* output)
{
// Prepare usb device for transfer
libusb_device_handle* hand;
int openres = libusb_open((libusb_device*) handle, &hand);
if(openres) return -1;
libusb_detach_kernel_driver(hand, 0x2);
// TODO : handle errors
driver_mouse_dpi_get(handle, hand, output);
driver_mouse_polling_rate_get(handle, hand, output);
driver_mouse_motion_sync_get(handle, hand, output);
driver_mouse_angle_snap_get(handle, hand, output);
libusb_attach_kernel_driver(hand, 0x2);
libusb_close(hand);
return 0;
}
int driver_wireless_connection_type_get(void* handle, struct WIRELESS* output)
{
output->connection_wireless = is_connection_wireless(handle);
return 0;
}
int driver_wireless_battery_state_get(void* handle, libusb_device_handle* hand, struct WIRELESS* output)
{
// Send command
struct REPORT_BATTERY report = {0};
int res = send_command(hand, COMMAND_BATTERY, REPORT_BATTERY_SIZE, &report, DEVICE_WIRELESS(handle));
if(res <= 0) return -1;
// Format output values
output->battery_level = report.battery_level;
output->charging = report.battery_status;
return 0;
}
int driver_wireless_get(void* handle, struct WIRELESS* output)
{
libusb_device* dev = handle;
// Prepare usb device for transfer
libusb_device_handle* hand;
int openres = libusb_open(dev, &hand);
if(openres) return -1;
libusb_detach_kernel_driver(hand, 0x2);
// TODO : handle errors
driver_wireless_battery_state_get(handle, hand, output);
driver_wireless_connection_type_get(handle, output);
// Close and return
libusb_attach_kernel_driver(hand, 0x2);
libusb_close(hand);
return 0;
}

View File

@@ -1,3 +1,6 @@
#ifndef G_WOLVES_H
#define G_WOLVES_H
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
@@ -9,16 +12,20 @@
#include <assert.h>
#include <unistd.h>
// Driver interface
#include "../../src/device/driver_interface.h"
// Devices
#include "devices/htx.h"
#include "devices/hts_plus.h"
#include "devices/hsk_pro.h"
#define MANUFACTURER_NAME "G-Wolves"
#define VENDOR_ID 0x33e4
#define REPORT_MAX_SIZE 0x40 // 64 Bytes
#define REPORT_UNKNOWN_0_SIZE 0x2
#define REPORT_POLLING_RATE_SIZE 0x2
#define REPORT_DPI_SETTINGS_SIZE 0x39 // 2b with old software, 5 profiles to 7 ?
#define REPORT_UNKNOWN_1_SIZE 0x2
#define REPORT_UNKNOWN_2_SIZE 0x24
@@ -31,7 +38,7 @@
#define REPORT_WIRELESS_MOUSE_SIZE 0x1
#define REPORT_WIRELESS_FIRMWARE_VERSION_SIZE 0x4 // -> 0x7
#define COMMAND_UNKNOWN_0 0x82
#define COMMAND_POLLING_RATE 0x82
#define COMMAND_DPI_SETTINGS 0x83
#define COMMAND_UNKNOWN_1 0x92
#define COMMAND_UNKNOWN_2 0x84
@@ -49,6 +56,7 @@ typedef struct DPI_LEVEL
uint8_t dpi_x_high;
uint8_t dpi_x_low;
// Older firmware does not support y dpi (only 1 dpi value)
uint8_t dpi_y_high;
uint8_t dpi_y_low;
@@ -79,16 +87,36 @@ struct REPORT_DPI_SETTINGS
} __attribute__((packed));
static_assert(sizeof(struct REPORT_DPI_SETTINGS) == REPORT_MAX_SIZE, "Report DPI_SETTINGS not of right size");
struct REPORT_UNKNOWN_0
struct REPORT_POLLING_RATE
{
report_header_t header;
/*
* HTX 4K, wireless/wired:
* 4000 Hz: 40
* 2000 Hz: 20
* 1000 Hz: 01
* 500 Hz: 02
* 250 Hz: 04
* 125 Hz: 08
* Warning: values are weird when wireless+wired plugged
*/
uint8_t value0;
/*
* HTX 4K, wireless / wired:
* 4000 Hz: 00 / --
* 2000 Hz: 87 / --
* 1000 Hz: 3b / 00
* 500 Hz: 08 / 00
* 250 Hz: 00 / 00
* 125 Hz: 3b / 00
*/
uint8_t value1;
uint8_t zeros[REPORT_MAX_SIZE - sizeof(report_header_t) - 2];
} __attribute__((packed));
static_assert(sizeof(struct REPORT_UNKNOWN_0) == REPORT_MAX_SIZE, "Report UNKNOWN_0 not of right size");
static_assert(sizeof(struct REPORT_POLLING_RATE) == REPORT_MAX_SIZE, "Report POLLING_RATE not of right size");
struct REPORT_UNKNOWN_1
{
@@ -195,3 +223,5 @@ struct REPORT_WIRELESS_FIRMWARE_VERSION
uint8_t zeros[REPORT_MAX_SIZE - sizeof(report_header_t) - 7];
} __attribute__((packed));
static_assert(sizeof(struct REPORT_WIRELESS_FIRMWARE_VERSION) == REPORT_MAX_SIZE, "Report WIRELESS_FIRMWARE_VERSION not of right size");
#endif

13
drivers/razer/Makefile Normal file
View File

@@ -0,0 +1,13 @@
CC=gcc
CFLAGS=$(shell pkg-config --cflags libusb-1.0) -O3 -Wall -shared
LDFLAGS=$(shell pkg-config --libs libusb-1.0)
FINAL_ASSETS=$(BUILD_DIR)/drivers/assets/razer-viper_mini.png
BUILD_DIR=../../build/
all: $(BUILD_DIR)/drivers/razer.so $(FINAL_ASSETS)
$(BUILD_DIR)/drivers/razer.so: razer.c | $(BUILD_DIR)/drivers/
$(CC) $(CFLAGS) -o $@ $^
$(BUILD_DIR)/drivers/assets/%.png: %.png | $(BUILD_DIR)/drivers/assets
cp $^ $@

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

106
drivers/razer/razer.c Normal file
View File

@@ -0,0 +1,106 @@
#include "razer.h"
static uint8_t compute_crc(void* report)
{
uint8_t* r = report;
uint8_t crc = 0;
for(size_t i = 2; i < REPORT_SIZE - sizeof(report_footer_t); i++)
{
crc ^= r[i];
}
return crc;
}
static int send_command(libusb_device_handle* hand, uint8_t command_class, uint8_t command, uint8_t size, void* report)
{
report_header_t* head = report;
head->report_size = size;
head->command_class = command_class;
head->command_id = command;
report_footer_t* footer = report + REPORT_SIZE - sizeof(report_footer_t);
footer->crc = compute_crc(report);
// Send command
int res = libusb_control_transfer(hand,
0x21, // request type
0x09, // request (SET_REPORT)
0x300, // wValue (FEATURE << 8 | REPORT(0))
0x0, // wIndex = 0x0 : interface
report,
REPORT_SIZE, // wLength = 90
0 /* timeout*/);
if(res <= 0) return res;
// Wait for the mouse to catch the report
usleep(100);
// Get response report
res = libusb_control_transfer(hand,
0xa1, // request type
0x01, // request (GET_REPORT)
0x300, // wValue (FEATURE << 8 | REPORT(0))
0x0, // wIndex = 0x0 : interface
report,
REPORT_SIZE, // wLength = 90
0);
return res;
}
void driver_init(void) {}
uint32_t driver_getkey(void)
{
static int count = 0;
count++;
// Register ALL razer compatible devices
uint16_t id = 0;
switch(count)
{
case 1:
id = VIPER_MINI_PRODUCT_ID;
break;
default:
id = 0;
}
if(id == 0) return 0;
return (VENDOR_ID << 16) | id;
}
char* driver_get_name(void* handle)
{
libusb_device* dev = handle;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(dev, &desc);
switch(desc.idProduct)
{
case VIPER_MINI_PRODUCT_ID:
return VIPER_MINI_NAME;
default:
return "Unknown Razer mice";
}
}
char* driver_get_image(void* handle)
{
libusb_device* dev = handle;
struct libusb_device_descriptor desc;
libusb_get_device_descriptor(dev, &desc);
switch(desc.idProduct)
{
case VIPER_MINI_PRODUCT_ID:
return VIPER_MINI_IMAGE;
default:
return "";
}
}

153
drivers/razer/razer.h Normal file
View File

@@ -0,0 +1,153 @@
#ifndef RAZER_H
#define RAZER_H
#include <libusb.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <stdbool.h>
// Driver interface
#include "../../src/device/driver_interface.h"
#define VENDOR_ID 0x1532
#define REPORT_SIZE 90
/* Devices */
#include "viper_mini.h"
typedef struct REPORT_HEADER
{
uint8_t status;
uint8_t transaction_id;
uint8_t remaining_packets_high;
uint8_t remaining_packets_low;
uint8_t protocol_type; // 0x0
uint8_t report_size;
uint8_t command_class;
uint8_t command_id;
} __attribute__((packed)) report_header_t;
typedef struct REPORT_FOOTER
{
uint8_t crc; // Control, xored bytes of report
uint8_t reserved;
} __attribute__((packed)) report_footer_t;
/* Commands of the BASIC (0x0) class */
#define COMMAND_CLASS_BASIC 0x0
struct REPORT_FIRMWARE_VERSION
{
report_header_t header;
uint8_t version_major;
uint8_t version_minor;
uint8_t zeros[REPORT_SIZE - sizeof(report_header_t) - sizeof(report_footer_t) - 2];
report_footer_t footer;
} __attribute__((packed));
#define REPORT_SIZE_FIRMWARE_VERSION 0x2
#define COMMAND_REPORT_FIRMWARE_VERSION 0x81
static_assert(sizeof(struct REPORT_FIRMWARE_VERSION) == REPORT_SIZE, "REPORT_FIRMWARE_VERSION is not of the right size");
struct REPORT_POLLING_RATE
{
report_header_t header;
// There is another report for up to 8000Hz polling
// 0x01: 1000Hz, 0x2: 500Hz, 0x3: 125Hz
uint8_t polling_rate;
uint8_t zeros[REPORT_SIZE - sizeof(report_header_t) - sizeof(report_footer_t) - 1];
report_footer_t footer;
} __attribute__((packed));
static_assert(sizeof(struct REPORT_POLLING_RATE) == REPORT_SIZE, "REPORT_POLLING_RATE is not of the right size");
#define REPORT_SIZE_POLLING_RATE 0x1
#define COMMAND_REPORT_POLLING_RATE 0x85
/* Commands of the DPI (0x4) class */
#define COMMAND_CLASS_DPI 0x4
struct REPORT_DPI
{
report_header_t header;
uint8_t variable_storage;
uint8_t dpi_x_high;
uint8_t dpi_x_low;
uint8_t dpi_y_high;
uint8_t dpi_y_low;
uint8_t unknown_0;
uint8_t unknown_1;
uint8_t zeros[REPORT_SIZE - sizeof(report_header_t) - sizeof(report_footer_t) - 7];
report_footer_t footer;
} __attribute__((packed));
static_assert(sizeof(struct REPORT_DPI) == REPORT_SIZE, "REPORT_DPI is not of the right size");
#define REPORT_SIZE_DPI 0x07
#define COMMAND_REPORT_DPI 0x85
typedef struct DPI_STAGE
{
uint8_t stage_index;
uint8_t dpi_x_high;
uint8_t dpi_x_low;
uint8_t dpi_y_high;
uint8_t dpi_y_low;
uint8_t unknown_0;
uint8_t unknown_1;
} __attribute__((packed)) dpi_stage_t;
struct REPORT_DPI_STAGES
{
report_header_t header;
uint8_t variable_storage;
uint8_t active_dpi_stage;
uint8_t dpi_stages_count;
dpi_stage_t stages[5];
uint8_t zeros[REPORT_SIZE - sizeof(report_header_t) - sizeof(report_footer_t) - 38];
report_footer_t footer;
} __attribute__((packed));
static_assert(sizeof(struct REPORT_DPI_STAGES) == REPORT_SIZE, "REPORT_DPI_STAGES is not of the right size");
#define REPORT_SIZE_DPI_STAGES 0x26
#define COMMAND_REPORT_DPI_STAGES 0x86
/* Commands of the BATTERY (0x7) class */
#define COMMAND_CLASS_BATTERY 0x7
struct REPORT_BATTERY_LEVEL
{
report_header_t header;
uint8_t ignored;
uint8_t battery_level; // 0-255
report_footer_t footer;
} __attribute__((packed));
#define REPORT_SIZE_BATTERY_LEVEL 0x2
#define COMMAND_REPORT_BATTERY_LEVEL 0x80
struct REPORT_BATTERY_STATUS
{
report_header_t header;
uint8_t ignored;
uint8_t charging; // 0: not charging, 1:charging
report_footer_t footer;
} __attribute__((packed));
#define REPORT_SIZE_BATTERY_STATUS 0x2
#define COMMAND_REPORT_BATTERY_STATUS 0x84
#endif

View File

@@ -0,0 +1,8 @@
#ifndef VIPER_MINI_H
#define VIPER_MINI_H
#define VIPER_MINI_PRODUCT_ID 0x008a
#define VIPER_MINI_NAME "Viper Mini"
#define VIPER_MINI_IMAGE "razer-viper_mini.png"
#endif

View File

@@ -3,6 +3,7 @@
#include "usb/usb.h"
#include <dlfcn.h>
#include <string.h>
#include <stdio.h>
struct DEVICE
{
@@ -40,6 +41,14 @@ device_t* device_register(void* driver, void* handle)
char* device_get_image(device_t* device)
{
char* (*getimage_fn)(void*) = dlsym(device->driver, "driver_get_image");
if(!getimage_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_get_image`\n");
char* tr = malloc(1);
*tr = 0;
return tr;
}
char* image = getimage_fn(device->handle);
size_t image_len = strlen(image);
@@ -58,5 +67,58 @@ char* device_get_image(device_t* device)
char* device_get_name(device_t* device)
{
char* (*getname_fn)(void*) = dlsym(device->driver, "driver_get_name");
if(!getname_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_get_name`\n");
return "";
}
return getname_fn(device->handle);
}
device_capacity_t device_get_capacity(device_t* device)
{
device_capacity_t (*getcapacity_fn)(void*) = dlsym(device->driver, "driver_get_capacity");
if(!getcapacity_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_get_capacity`\n");
return 0;
}
return getcapacity_fn(device->handle);
}
char* device_get_manufacturer(device_t* device)
{
char* (*getmanufacturer_fn)(void*) = dlsym(device->driver, "driver_get_manufacturer");
if(!getmanufacturer_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_get_manufacturer`\n");
return "";
}
return getmanufacturer_fn(device->handle);
}
int device_mouse_get(device_t* device, struct MOUSE* output)
{
int (*mouseget_fn)(void*, struct MOUSE*) = dlsym(device->driver, "driver_mouse_get");
if(!mouseget_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_mouse_get`\n");
return -1;
}
return mouseget_fn(device->handle, output);
}
int device_wireless_get(device_t* device, struct WIRELESS* output)
{
int (*wirelessget_fn)(void*, struct WIRELESS*) = dlsym(device->driver, "driver_wireless_get");
if(!wirelessget_fn)
{
fprintf(stderr, "Warning: driver does not implement `driver_wireless_get`\n");
return -1;
}
return wirelessget_fn(device->handle, output);
}

View File

@@ -2,6 +2,10 @@
#define DEVICE_H
#include <stdlib.h>
#include <dlfcn.h>
#include "driver_interface.h"
#include "utils/array.h"
typedef struct DEVICE device_t;
@@ -14,5 +18,8 @@ array_t* device_get_array();
device_t* device_register(void* driver, void* handle);
char* device_get_image(device_t* device);
char* device_get_name(device_t* device);
device_capacity_t device_get_capacity(device_t* device);
char* device_get_manufacturer(device_t* device);
int device_mouse_get(device_t* device, struct MOUSE* output);
#endif

View File

@@ -0,0 +1,58 @@
#ifndef DRIVER_INTERFACE_H
#define DRIVER_INTERFACE_H
#include <stdint.h>
#include <stdbool.h>
typedef enum DEVICE_CAPACITY
{
DEVICE_CAPACITY_MOUSE = (1 << 0),
DEVICE_CAPACITY_KEYBOARD = (1 << 1),
DEVICE_CAPACITY_WIRELESS = (1 << 10),
} device_capacity_t;
void driver_init(); // Initialization
uint32_t driver_getkey(); // Calls to this function must return USB keys that this driver registers, until 0
device_capacity_t driver_get_capacity(void* handle); // Returns device capacity (see above)
char* driver_get_name(void* handle); // Returns peripheral name
char* driver_get_manufacturer(void* handle); // Returns peripheral manufacturer
char* driver_get_image(void* handle); // Returns peripheral image
/* Mouse drivers */
struct MOUSE
{
// DPI
unsigned int max_level_count;
unsigned int level_count;
unsigned int level_current;
bool led_available;
bool xy_available;
unsigned int dpi_x[8];
unsigned int dpi_y[8];
unsigned char r[8];
unsigned char g[8];
unsigned char b[8];
// Polling rate
unsigned int polling_rate_count;
unsigned int polling_rate_current;
unsigned int polling_rates[8];
// Features
bool angle_snap;
bool motion_sync;
};
int driver_mouse_get(void* handle, struct MOUSE* output);
int driver_mouse_set(void* handle, struct MOUSE* input);
/* Wireless drivers */
struct WIRELESS
{
int battery_level;
bool charging;
bool connection_wireless;
};
int driver_wireless_get(void* handle, struct WIRELESS* output);
#endif

View File

@@ -1,4 +1,5 @@
#include "ui/ginput-application.h"
#include <adwaita.h>
#include "vala.h"
#include "device/device.h"
int main(gint argc, gchar** argv)

65
src/ui/Device.vapi Normal file
View File

@@ -0,0 +1,65 @@
[CCode (cheader_filename = "utils/array.h")]
namespace Array
{
void* get(void* array, size_t index);
size_t count(void* array);
}
[CCode (cheader_filename = "device/device.h")]
namespace Device
{
// Obtain device array
void* get_array();
[CCode (cname = "device_capacity_t", cprefix = "DEVICE_CAPACITY_", has_type_id = false)]
public enum Capacity
{
MOUSE = (1 << 0),
KEYBOARD = (1 << 1),
WIRELESS = (1 << 10)
}
// Handle a specific device
unowned string get_name(void* device);
string get_image(void* device);
int get_capacity(void* device);
unowned string get_manufacturer(void* device);
[CCode (cname = "struct MOUSE", free_function = "", has_type_id = false)]
public struct Mouse
{
// DPI
uint max_level_count;
uint level_count;
uint level_current;
bool led_available;
bool xy_available;
uint dpi_x[8];
uchar r[8];
uchar g[8];
uchar b[8];
// Polling rate
uint polling_rate_count;
uint polling_rate_current;
uint polling_rates[8];
// Features
bool angle_snap;
bool motion_sync;
}
public int mouse_get(void* device, Mouse* output);
public int mouse_set(void* device, Mouse* input);
[CCode (cname = "struct WIRELESS", free_function = "", has_type_id = false)]
public struct Wireless
{
int battery_level;
bool charging;
bool connection_wireless;
}
public int wireless_get(void* device, Wireless* output);
public int wireless_set(void* device, Wireless* input);
}

View File

@@ -0,0 +1,11 @@
public class GinputApplication : Adw.Application {
public GinputApplication() {
Object(application_id: "v.ginput", flags:ApplicationFlags.DEFAULT_FLAGS);
}
public override void activate() {
var win = new MainWindow(this);
win.present();
}
}

80
src/ui/MainWindow.vala Normal file
View File

@@ -0,0 +1,80 @@
using Device;
[GtkTemplate (ui="/v/ginput/main-window.ui")]
public class MainWindow : Adw.ApplicationWindow
{
[GtkChild]
protected unowned Gtk.ListBox device_list;
[GtkChild]
protected unowned Gtk.Stack stack;
protected Panel active_panel;
public MainWindow(Adw.Application app)
{
Object(application:app, resizable:true, title:"Input devices", icon_name:"ginput-icon", show_menubar:false);
}
construct
{
init_template();
set_default_size(1024, 768);
device_list.row_selected.connect(select_device);
}
void activate_panel(Panel panel)
{
var pname = panel.get_name();
if(stack.get_child_by_name(pname) == null)
stack.add_named(panel, pname);
stack.set_visible_child_name(pname);
active_panel = panel;
}
void select_device(Gtk.ListBox list, Gtk.ListBoxRow? row)
{
if(row == null)
return;
void* device = row.get_data("ginput_device");
var name = Device.get_name(device);
Panel devpane;
var capacity = Device.get_capacity(device);
if((capacity & Device.Capacity.MOUSE) != 0)
devpane = new MousePanel(name, device);
else
{
stderr.printf("Warning: selected device with unsupported capacity\n");
return;
}
activate_panel(devpane);
}
public void add_device(void* device)
{
Gtk.ListBoxRow row = new Gtk.ListBoxRow();
Gtk.Label label = new Gtk.Label(Device.get_name(device));
label.add_css_class("body");
label.set_margin_top(7);
label.set_margin_bottom(7);
row.set_child(label);
row.set_data("ginput_device", device);
device_list.append(row);
}
public override void constructed()
{
// Add all devices to the window
var devices = Device.get_array();
for(size_t i = 0; i < Array.count(devices); i++)
add_device(Array.get(devices, i));
// Activate empty panel by default
activate_panel(new EmptyPanel());
base.constructed();
}
}

38
src/ui/Panel.vala Normal file
View File

@@ -0,0 +1,38 @@
[GtkTemplate (ui="/v/ginput/panel.ui")]
public abstract class Panel : Adw.Bin, Gtk.Buildable
{
[GtkChild]
protected unowned Adw.Bin content_bin;
[GtkChild]
protected unowned Gtk.Box main_box;
[GtkChild]
protected unowned Adw.Bin titlebar_bin;
[GtkChild]
protected unowned Adw.HeaderBar titlebar;
protected Adw.WindowTitle titlebar_title;
protected Panel(string name)
{
this.name = name;
titlebar_title = new Adw.WindowTitle(this.name, "");
titlebar.set_title_widget(titlebar_title);
}
construct
{
init_template();
}
void add_child(Gtk.Builder builder, Object child, string? type)
{
if(type != null && type == "content")
{
content_bin.set_child(((Gtk.Widget) child));
return;
}
base.add_child(builder, child, type);
}
}

View File

@@ -1,87 +0,0 @@
#include "ginput-application.h"
#include "ui/main-window.h"
struct _GinputApplication
{
AdwApplication parent;
MainWindow* window;
};
G_DEFINE_TYPE(GinputApplication, ginput_application, ADW_TYPE_APPLICATION)
/*
* Special 'activate' trigger of the application, called when the application is launched
* This shows the main window
*/
static void
ginput_application_activate(GApplication* application)
{
GinputApplication* self = GINPUT_APPLICATION(application);
gtk_window_present(GTK_WINDOW(self->window));
}
static void
ginput_application_startup(GApplication* application)
{
GinputApplication* self = GINPUT_APPLICATION(application);
G_APPLICATION_CLASS(ginput_application_parent_class)->startup(application);
self->window = main_window_new(ADW_APPLICATION(application));
GtkCssProvider* provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(provider, "/v/ginput/style.css");
gtk_style_context_add_provider_for_display(gdk_display_get_default(),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
static void
ginput_application_finalize(GObject* object)
{
G_OBJECT_CLASS(ginput_application_parent_class)->finalize(object);
}
static GObject*
ginput_application_constructor(GType type, guint n_construct_params, GObjectConstructParam* construct_params)
{
static GObject* self = NULL;
if(self == NULL)
{
self = G_OBJECT_CLASS(ginput_application_parent_class)->constructor(type, n_construct_params, construct_params);
g_object_add_weak_pointer(self, (gpointer) &self);
return self;
}
return g_object_ref(self);
}
static void
ginput_application_class_init(GinputApplicationClass* klass)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
GApplicationClass* application_class = G_APPLICATION_CLASS(klass);
object_class->finalize = ginput_application_finalize;
object_class->constructor = ginput_application_constructor;
application_class->activate = ginput_application_activate;
application_class->startup = ginput_application_startup;
// application_class->command_line = ginput_application_command_line;
// application_class->handle_local_options = ginput_application_handle_local_options;
}
static void
ginput_application_init(GinputApplication* self)
{
}
AdwApplication*
ginput_application_new(void)
{
return g_object_new(ginput_application_get_type(),
"application-id", "v.ginput",
"flags", G_APPLICATION_DEFAULT_FLAGS,
NULL);
}

View File

@@ -1,11 +0,0 @@
#pragma once
#include <adwaita.h>
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(GinputApplication, ginput_application, GINPUT, APPLICATION, AdwApplication)
AdwApplication* ginput_application_new(void);
G_END_DECLS

View File

@@ -1,314 +0,0 @@
#define G_LOG_DOMAIN "main-window"
#include "main-window.h"
#include "panels/empty-panel.h"
#include "panels/mouse-panel.h"
#include "device/device.h"
#include <gtk/gtk.h>
struct _MainWindow
{
AdwApplicationWindow parent;
AdwHeaderBar* header;
AdwLeaflet* main_leaflet;
GtkListBox *device_list;
GtkBox* sidebar_box;
AdwWindowTitle* sidebar_title_widget;
GtkStack* stack;
GtkWidget* current_panel;
char* current_panel_id;
GtkWidget* custom_titlebar;
// CcShellModel *store;
Panel *active_panel;
GSettings* settings;
gboolean folded;
// CcPanelListView previous_list_view;
};
G_DEFINE_TYPE(MainWindow, main_window, ADW_TYPE_APPLICATION_WINDOW)
enum
{
PROP_0,
PROP_ACTIVE_PANEL,
PROP_MODEL,
PROP_FOLDED,
};
static gboolean activate_panel(MainWindow* self, Panel* panel, const gchar* id)
{
self->current_panel = GTK_WIDGET(panel);
self->active_panel = panel;
gtk_stack_add_named(self->stack, self->current_panel, id);
gtk_stack_set_visible_child_name (self->stack, id);
}
/*
* @Override from GtkWidget
*/
static void
main_window_map(GtkWidget* widget)
{
MainWindow* self = (MainWindow*) widget;
GTK_WIDGET_CLASS(main_window_parent_class)->map(widget);
}
/*
* @Override from GtkWidget
*/
static void
main_window_unmap(GtkWidget* widget)
{
MainWindow* self = GINPUT_MAIN_WINDOW(widget);
gboolean maximized;
gint height;
gint width;
maximized = gtk_window_is_maximized(GTK_WINDOW(self));
gtk_window_get_default_size(GTK_WINDOW(self), &width, &height);
g_settings_set(self->settings,
"window-state",
"(iib)",
width,
height,
maximized);
GTK_WIDGET_CLASS(main_window_parent_class)->unmap(widget);
}
static void
main_window_get_property(GObject* object, guint property_id, GValue* value, GParamSpec* pspec)
{
MainWindow* self = GINPUT_MAIN_WINDOW(object);
switch(property_id)
{
case PROP_ACTIVE_PANEL:
g_value_set_object (value, self->active_panel);
break;
// case PROP_MODEL:
// g_value_set_object (value, self->store);
// break;
case PROP_FOLDED:
g_value_set_boolean(value, self->folded);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}
}
static void
main_window_set_property(GObject* object,
guint property_id,
const GValue* value,
GParamSpec* pspec)
{
MainWindow* self = GINPUT_MAIN_WINDOW(object);
switch(property_id)
{
// case PROP_ACTIVE_PANEL:
// set_active_panel(self, g_value_get_object(value));
// break;
// case PROP_MODEL:
// g_assert(self->store == NULL);
// self->store = g_value_dup_object(value);
// break;
case PROP_FOLDED:
self->folded = g_value_get_boolean(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
}
}
static void
load_window_state(MainWindow* self)
{
gint current_width = -1;
gint current_height = -1;
gboolean maximized = FALSE;
g_settings_get(self->settings,
"window-state",
"(iib)",
&current_width,
&current_height,
&maximized);
if(current_width != -1 && current_height != -1)
gtk_window_set_default_size(GTK_WINDOW(self), current_width, current_height);
if(maximized)
gtk_window_maximize(GTK_WINDOW(self));
}
void main_window_add_all_devices(MainWindow* self)
{
// Obtain devices, and iterate over the array to add them
array_t* devices = device_get_array();
for(size_t i = 0; i < array_count(devices); i++)
main_window_add_device_to_list(self, array_get(devices, i));
}
void main_window_device_selected(GtkListBox* self, GtkListBoxRow* row, gpointer user_data)
{
MainWindow* main_window = (MainWindow*) gtk_widget_get_root(GTK_WIDGET(self));
// Selection cleared
if(!row)
{
// TODO: set to empty panel ?
return;
}
device_t* device = (device_t*) g_object_get_data(G_OBJECT(row), "ginput_device");
MousePanel* mp = (Panel*) mouse_panel_new();
mouse_panel_set_device(mp, device);
// TODO : change and use already added child if possible
activate_panel(main_window, GINPUT_PANEL(mp), device_get_name(device));
}
static void
main_window_constructed(GObject* object)
{
MainWindow* self = GINPUT_MAIN_WINDOW(object);
load_window_state(self);
main_window_add_all_devices(self);
/* Add the panels */
// setup_model (self);
/* After everything is loaded, select the last used panel, if any,
* or the first visible panel. We do that in an idle handler so we
* have a chance to skip it when another panel has been explicitly
* activated from commandline parameter or from DBus method */
// g_idle_add_once ((GSourceOnceFunc) maybe_load_last_panel, self);
// g_signal_connect_swapped (self->panel_list,
// "notify::view",
// G_CALLBACK (update_headerbar_buttons),
// self);
// update_headerbar_buttons (self);
// adw_leaflet_set_visible_child (self->main_leaflet,
// GTK_WIDGET (self->sidebar_box));
G_OBJECT_CLASS(main_window_parent_class)->constructed(object);
}
static void
main_window_dispose(GObject* object)
{
MainWindow* self = GINPUT_MAIN_WINDOW(object);
g_clear_pointer(&self->current_panel_id, g_free);
// g_clear_object (&self->store);
// g_clear_object (&self->active_panel);
G_OBJECT_CLASS(main_window_parent_class)->dispose(object);
}
static void
main_window_finalize(GObject* object)
{
MainWindow* self = GINPUT_MAIN_WINDOW(object);
g_clear_object(&self->settings);
G_OBJECT_CLASS(main_window_parent_class)->finalize(object);
}
static void
main_window_class_init(MainWindowClass* klass)
{
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
GObjectClass* object_class = G_OBJECT_CLASS(klass);
object_class->get_property = main_window_get_property;
object_class->set_property = main_window_set_property;
object_class->constructed = main_window_constructed;
object_class->dispose = main_window_dispose;
object_class->finalize = main_window_finalize;
widget_class->map = main_window_map;
widget_class->unmap = main_window_unmap;
g_object_class_install_property(object_class, PROP_FOLDED,
g_param_spec_boolean("folded", "Folded", "Whether the window is foled", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gtk_widget_class_set_template_from_resource(widget_class, "/v/ginput/main-window.ui");
gtk_widget_class_bind_template_child(widget_class, MainWindow, header);
gtk_widget_class_bind_template_child(widget_class, MainWindow, main_leaflet);
gtk_widget_class_bind_template_child(widget_class, MainWindow, device_list);
gtk_widget_class_bind_template_child(widget_class, MainWindow, sidebar_box);
gtk_widget_class_bind_template_child(widget_class, MainWindow, sidebar_title_widget);
gtk_widget_class_bind_template_child(widget_class, MainWindow, stack);
}
static void
main_window_init(MainWindow* self)
{
gtk_widget_init_template(GTK_WIDGET(self));
self->settings = g_settings_new("v.ginput");
g_object_bind_property(self->main_leaflet,
"folded",
self,
"folded",
G_BINDING_SYNC_CREATE);
activate_panel(self, empty_panel_new(), "empty");
g_signal_connect(self->device_list, "row_selected", G_CALLBACK(main_window_device_selected), NULL);
}
// TODO use translation
#define _(x) x
MainWindow*
main_window_new(AdwApplication* application)
{
g_return_val_if_fail(GTK_IS_APPLICATION(application), NULL);
return g_object_new(main_window_get_type(),
"application", application,
"resizable", TRUE,
"title", _("Input devices"),
"icon-name", "input-devices",
"show-menubar", FALSE,
NULL);
}
void main_window_add_device_to_list(MainWindow* self, device_t* device)
{
// Setup row
GtkListBoxRow* row = (GtkListBoxRow*) gtk_list_box_row_new();
GtkLabel* label = (GtkLabel*) gtk_label_new(device_get_name(device));
gtk_widget_add_css_class(GTK_WIDGET(label), "body");
gtk_widget_set_margin_top(GTK_WIDGET(label), 7);
gtk_widget_set_margin_bottom(GTK_WIDGET(label), 7);
gtk_list_box_row_set_child(row, GTK_WIDGET(label));
g_object_set_data(G_OBJECT(row), "ginput_device", device);
// Add row to listbox
gtk_list_box_append(GTK_LIST_BOX(self->device_list), GTK_WIDGET(row));
}

View File

@@ -1,14 +0,0 @@
#pragma once
#include <adwaita.h>
#include "device/device.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(MainWindow, main_window, GINPUT, MAIN_WINDOW, AdwApplicationWindow)
MainWindow* main_window_new(AdwApplication* application);
void main_window_add_device_to_list(MainWindow* self, device_t* device);
G_END_DECLS

View File

@@ -1,206 +0,0 @@
#include "panel.h"
/*
* Abstract class representing a panel
*/
typedef struct
{
AdwBin* content_bin;
GtkBox* main_box;
AdwBin* titlebar_bin;
AdwHeaderBar* titlebar;
GCancellable* cancellable;
gboolean folded;
gchar* title;
} PanelPrivate;
static void panel_buildable_init(GtkBuildableIface* iface);
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(Panel, panel, ADW_TYPE_BIN,
G_ADD_PRIVATE(Panel)
G_IMPLEMENT_INTERFACE(GTK_TYPE_BUILDABLE, panel_buildable_init))
static GtkBuildableIface* parent_buildable_iface;
enum
{
PROP_0,
PROP_PARAMETERS,
PROP_FOLDED,
PROP_TITLE,
N_PROPS
};
static GParamSpec* properties[N_PROPS];
static void
panel_buildable_add_child(GtkBuildable* buildable, GtkBuilder* builder, GObject* child, const char* type)
{
PanelPrivate* priv = panel_get_instance_private(GINPUT_PANEL(buildable));
if(GTK_IS_WIDGET(child) && !priv->main_box)
{
adw_bin_set_child(ADW_BIN(buildable), GTK_WIDGET(child));
return;
}
if(g_strcmp0(type, "content") == 0)
adw_bin_set_child(priv->content_bin, GTK_WIDGET(child));
else if(g_strcmp0(type, "titlebar-start") == 0)
adw_header_bar_pack_start(priv->titlebar, GTK_WIDGET(child));
else if(g_strcmp0(type, "titlebar-end") == 0)
adw_header_bar_pack_end(priv->titlebar, GTK_WIDGET(child));
else if(g_strcmp0(type, "titlebar") == 0)
adw_bin_set_child(priv->titlebar_bin, GTK_WIDGET(child));
else
parent_buildable_iface->add_child(buildable, builder, child, type);
}
/*
* @Override from GtkBuildableIface
*/
static void
panel_buildable_init(GtkBuildableIface* iface)
{
parent_buildable_iface = g_type_interface_peek_parent(iface);
iface->add_child = panel_buildable_add_child;
}
/*
* @Override from GObject
*/
static void
panel_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec)
{
PanelPrivate* priv = panel_get_instance_private(GINPUT_PANEL(object));
switch(prop_id)
{
case PROP_PARAMETERS:
{
g_autoptr(GVariant) v = NULL;
GVariant* parameters;
gsize n_parameters;
parameters = g_value_get_variant(value);
if(parameters == NULL)
return;
n_parameters = g_variant_n_children(parameters);
if(n_parameters == 0)
return;
g_variant_get_child(parameters, 0, "v", &v);
if(!g_variant_is_of_type(v, G_VARIANT_TYPE_DICTIONARY))
g_warning("Wrong type for the first argument GVariant, expected 'a{sv}' but got '%s'",
(gchar*) g_variant_get_type(v));
else if(g_variant_n_children(v) > 0)
g_warning("Ignoring additional flags");
if(n_parameters > 1)
g_warning("Ignoring additional parameters");
break;
}
case PROP_TITLE:
priv->title = g_value_dup_string(value);
break;
case PROP_FOLDED:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*
* @Override from GObject
*/
static void
panel_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec)
{
PanelPrivate* priv = panel_get_instance_private(GINPUT_PANEL(object));
switch(prop_id)
{
case PROP_FOLDED:
g_value_set_boolean(value, priv->folded);
break;
case PROP_TITLE:
g_value_set_string(value, priv->title);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*
* @Override from GObject
*/
static void
panel_finalize(GObject* object)
{
PanelPrivate* priv = panel_get_instance_private(GINPUT_PANEL(object));
g_cancellable_cancel(priv->cancellable);
g_clear_object(&priv->cancellable);
g_clear_pointer(&priv->title, g_free);
G_OBJECT_CLASS(panel_parent_class)->finalize(object);
}
static void
panel_class_init(PanelClass* klass)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
object_class->get_property = panel_get_property;
object_class->set_property = panel_set_property;
object_class->finalize = panel_finalize;
// signals[SIDEBAR_ACTIVATED] = g_signal_new ("sidebar-activated",
// G_TYPE_FROM_CLASS (object_class),
// G_SIGNAL_RUN_LAST,
// 0, NULL, NULL,
// g_cclosure_marshal_VOID__VOID,
// G_TYPE_NONE, 0);
properties[PROP_FOLDED] = g_param_spec_boolean("folded", NULL, NULL,
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
properties[PROP_PARAMETERS] = g_param_spec_variant("parameters",
"Structured parameters",
"Additional parameters passed externally (ie. command line, D-Bus activation)",
G_VARIANT_TYPE("av"),
NULL,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
properties[PROP_TITLE] = g_param_spec_string("title", NULL, NULL, NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, N_PROPS, properties);
gtk_widget_class_set_template_from_resource(widget_class, "/v/ginput/panel.ui");
gtk_widget_class_bind_template_child_private(widget_class, Panel, content_bin);
gtk_widget_class_bind_template_child_private(widget_class, Panel, main_box);
gtk_widget_class_bind_template_child_private(widget_class, Panel, titlebar_bin);
gtk_widget_class_bind_template_child_private(widget_class, Panel, titlebar);
}
static void
panel_init(Panel* panel)
{
gtk_widget_init_template(GTK_WIDGET(panel));
}

View File

@@ -1,44 +0,0 @@
#pragma once
#include <adwaita.h>
G_DECLARE_DERIVABLE_TYPE(Panel, panel, GINPUT, PANEL, AdwBin)
G_BEGIN_DECLS
/**
* PanelClass:
*
* The contents of this struct are private and should not be accessed directly.
*/
struct _PanelClass
{
/*< private >*/
AdwBinClass parent_class;
const gchar* (*get_help_uri)(Panel* panel);
GtkWidget* (*get_sidebar_widget)(Panel* panel);
};
GPermission* panel_get_permission(Panel* panel);
const gchar* panel_get_help_uri(Panel* panel);
GtkWidget* panel_get_sidebar_widget(Panel* panel);
GCancellable* panel_get_cancellable(Panel* panel);
gboolean panel_get_folded(Panel* panel);
GtkWidget* panel_get_content(Panel* panel);
void panel_set_content(Panel* panel, GtkWidget* content);
GtkWidget* panel_get_titlebar(Panel* panel);
void panel_set_titlebar(Panel* panel, GtkWidget* titlebar);
void panel_deactivate(Panel* panel);
G_END_DECLS

View File

@@ -0,0 +1,8 @@
[GtkTemplate (ui="/v/ginput/panel-empty.ui")]
public class EmptyPanel : Panel
{
public EmptyPanel()
{
base("");
}
}

View File

@@ -0,0 +1,122 @@
using Device;
[GtkTemplate (ui="/v/ginput/panel-mouse.ui")]
public class MousePanel : Panel
{
// Mouse presentation
[GtkChild]
private unowned Gtk.Image mouse_image;
[GtkChild]
private unowned Gtk.Label mouse_manufacturer;
[GtkChild]
private unowned Gtk.Label mouse_name;
// Wireless informations
[GtkChild]
private unowned Adw.PreferencesGroup wireless;
[GtkChild]
private unowned Gtk.LevelBar battery_level;
[GtkChild]
private unowned Gtk.Label battery_level_label;
[GtkChild]
private unowned Gtk.Label charging_state;
// DPI
[GtkChild]
private unowned Adw.PreferencesGroup dpi_preference_group;
[GtkChild]
private unowned Adw.ButtonContent dpi_stage_add_button;
// Features
[GtkChild]
private unowned Adw.SwitchRow motion_sync_switchrow;
[GtkChild]
private unowned Adw.SwitchRow angle_snap_switchrow;
// Polling rate
[GtkChild]
private unowned Adw.ComboRow polling_rate_comborow;
private void* device;
public MousePanel(string name, void* device)
{
base(name);
this.device = device;
refresh_device();
}
public void refresh_device()
{
// Obtain and fill basic information
mouse_image.set_from_file(Device.get_image(this.device));
mouse_name.set_label(name);
mouse_manufacturer.set_label(Device.get_manufacturer(this.device));
// Obtain mouse information
Mouse mouse = Mouse();
Device.mouse_get(this.device, &mouse);
/* Fill information */
// DPI stages
string dpi_used_max = "%u/%u".printf(mouse.level_count, mouse.max_level_count);
dpi_stage_add_button.set_label(dpi_used_max);
// All dpi levels
Gtk.CheckButton? group = null;
for(size_t i = 0; i < mouse.level_count; i++)
{
Adw.ActionRow row = new Adw.ActionRow();
// Add radiobutton
if(mouse.level_count > 1)
{
Gtk.CheckButton radio_button = new Gtk.CheckButton();
if(group == null) group = radio_button;
else radio_button.set_group(group);
if(mouse.level_current == i)
radio_button.set_active(true);
row.add_prefix(radio_button);
}
row.set_title("%u DPI".printf(mouse.dpi_x[i]));
dpi_preference_group.add(row);
}
// Features
motion_sync_switchrow.set_active(mouse.motion_sync);
angle_snap_switchrow.set_active(mouse.angle_snap);
// Polling rate
var model = new Gtk.StringList(null);
for(size_t i = 0; i < mouse.polling_rate_count; i++)
{
model.append("%u Hz".printf(mouse.polling_rates[i]));
}
polling_rate_comborow.set_model(model);
polling_rate_comborow.set_selected(mouse.polling_rate_current);
// Wireless
if((Device.get_capacity(this.device) & Device.Capacity.WIRELESS) != 0)
{
Wireless wireless = Wireless();
Device.wireless_get(this.device, &wireless);
battery_level.set_value(wireless.battery_level);
battery_level_label.set_label("%u %%".printf(wireless.battery_level));
if(wireless.charging)
charging_state.set_label("Charging");
else
charging_state.set_label("Not charging");
}
else
{
wireless.hide();
}
}
}

View File

@@ -1,30 +0,0 @@
#include "empty-panel.h"
struct _EmptyPanel
{
Panel parent_instance;
};
G_DEFINE_TYPE (EmptyPanel, empty_panel, panel_get_type())
static void
empty_panel_class_init(EmptyPanelClass* klass)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
PanelClass* panel_class = GINPUT_PANEL_CLASS(klass);
gtk_widget_class_set_template_from_resource(widget_class, "/v/ginput/panel-empty.ui");
}
static void
empty_panel_init(EmptyPanel* self)
{
gtk_widget_init_template(GTK_WIDGET(self));
}
Panel*
empty_panel_new(void)
{
return g_object_new(empty_panel_get_type(), NULL);
}

View File

@@ -1,11 +0,0 @@
#pragma once
#include "ui/panel.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(EmptyPanel, empty_panel, GINPUT, EMPTY_PANEL, Panel)
G_END_DECLS
Panel* empty_panel_new(void);

View File

@@ -1,44 +0,0 @@
#include "mouse-panel.h"
struct _MousePanel
{
Panel parent_instance;
GtkImage* mouse_image;
GtkLabel* mouse_name;
};
G_DEFINE_TYPE (MousePanel, mouse_panel, panel_get_type())
static void
mouse_panel_class_init(MousePanelClass* klass)
{
GObjectClass* object_class = G_OBJECT_CLASS(klass);
GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
PanelClass* panel_class = GINPUT_PANEL_CLASS(klass);
gtk_widget_class_set_template_from_resource(widget_class, "/v/ginput/panel-mouse.ui");
gtk_widget_class_bind_template_child(widget_class, MousePanel, mouse_image);
gtk_widget_class_bind_template_child(widget_class, MousePanel, mouse_name);
}
static void
mouse_panel_init(MousePanel* self)
{
gtk_widget_init_template(GTK_WIDGET(self));
}
Panel*
mouse_panel_new(void)
{
return g_object_new(mouse_panel_get_type(), NULL);
}
void mouse_panel_set_device(MousePanel* self, device_t* device)
{
char* image = device_get_image(device);
gtk_image_set_from_file(self->mouse_image, image);
gtk_label_set_label(self->mouse_name, device_get_name(device));
free(image);
}

View File

@@ -1,15 +0,0 @@
#pragma once
#include "ui/panel.h"
#include "device/device.h"
G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(MousePanel, mouse_panel, GINPUT, MOUSE_PANEL, Panel)
G_END_DECLS
Panel* mouse_panel_new(void);
void mouse_panel_set_device(MousePanel* self, device_t* device);

View File

@@ -1,88 +1,87 @@
<?xml version='1.0' encoding='UTF-8'?>
<interface>
<requires lib="gtk" version="4.10" />
<requires lib="libadwaita" version="1.0" />
<requires lib="libadwaita" version="1.1" />
<template class="MainWindow" parent="AdwApplicationWindow">
<property name="default-height">980</property>
<property name="default-width">640</property>
<child>
<object class="AdwLeaflet" id="main_leaflet">
<child>
<object class="GtkBox" id="sidebar_box">
<property name="orientation">vertical</property>
<child>
<object class="AdwHeaderBar" id="header">
<property name="show-end-title-buttons">False</property>
<property name="title-widget">
<object class="AdwWindowTitle" id="sidebar_title_widget">
<property name="title">Input devices</property>
</object>
</property>
<child type="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="primary">True</property>
<style>
<class name="image-button" />
</style>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<object class="AdwNavigationSplitView">
<property name="sidebar">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Input devices</property>
<child>
<object class="GtkScrolledWindow">
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkViewport">
<child>
<object class="GtkListBox" id="device_list">
<object class="AdwHeaderBar" id="header">
<property name="show-end-title-buttons">False</property>
<property name="title-widget">
<object class="AdwWindowTitle" id="sidebar_title_widget">
<property name="title">Input devices</property>
</object>
</property>
<child type="end">
<object class="GtkMenuButton">
<property name="icon-name">open-menu-symbolic</property>
<property name="primary">True</property>
<style>
<class name="image-button" />
</style>
<property name="menu-model">primary_menu</property>
</object>
</child>
</object>
</child>
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
<property name="width-request">200</property>
</object>
</child>
<child>
<object class="GtkScrolledWindow">
<child>
<object class="GtkViewport">
<child>
<object class="GtkListBox" id="device_list">
</object>
</child>
</object>
</child>
<property name="hscrollbar-policy">never</property>
<property name="vexpand">True</property>
<property name="width-request">200</property>
</object>
</child>
</object>
</child>
<child>
<object class="AdwLeafletPage">
<property name="child">
<object class="GtkSeparator">
<property name="orientation">vertical</property>
<style>
<class name="sidebar" />
</style>
</object>
</property>
<property name="navigatable">False</property>
</object>
</child>
<child>
<object class="GtkBox" id="panel_box">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="vexpand">True</property>
<child>
<object class="GtkStack" id="stack">
<property name="hexpand">True</property>
<property name="transition-type">crossfade</property>
<property name="vexpand">True</property>
<property name="width-request">360</property>
<style>
<class name="background" />
</style>
</object>
</child>
</object>
</child>
</property>
<property name="content">
<object class="AdwNavigationPage">
<property name="title">Empty</property>
<child>
<object class="GtkBox" id="panel_box">
<property name="hexpand">True</property>
<property name="orientation">vertical</property>
<property name="vexpand">True</property>
<child>
<object class="GtkStack" id="stack">
<property name="hexpand">True</property>
<property name="transition-type">crossfade</property>
<property name="vexpand">True</property>
<property name="width-request">360</property>
<style>
<class name="background" />
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>

View File

@@ -5,94 +5,277 @@
<child type="content">
<object class="GtkBox">
<property name="halign">start</property>
<property name="spacing">10</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="hexpand">false</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="presentation_box">
<object class="GtkBox">
<property name="halign">start</property>
<property name="orientation">vertical</property>
<property name="spacing">10</property>
<child>
<object class="GtkImage" id="mouse_image">
<property name="halign">start</property>
<property name="height-request">250</property>
<property name="margin-top">30</property>
<property name="valign">start</property>
<property name="width-request">300</property>
<style />
</object>
</child>
<child>
<object class="GtkLabel" id="mouse_name">
<property name="halign">center</property>
<property name="justify">center</property>
<style>
<class name="heading" />
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="basic_settings">
<property name="halign">start</property>
<property name="margin-top">30</property>
<property name="orientation">vertical</property>
<property name="valign">start</property>
<property name="hexpand">false</property>
<child>
<object class="GtkLabel">
<object class="GtkBox" id="presentation_box">
<property name="halign">start</property>
<property name="label">Basic settings</property>
<property name="valign">start</property>
<style>
<class name="title-3" />
</style>
</object>
</child>
<child>
<object class="GtkBox" id="dpi_box">
<property name="halign">start</property>
<property name="margin-top">30</property>
<property name="valign">start</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<object class="GtkImage" id="mouse_image">
<property name="halign">start</property>
<property name="height-request">250</property>
<property name="margin-top">30</property>
<property name="valign">start</property>
<property name="width-request">300</property>
<style />
</object>
</child>
<child>
<object class="GtkLabel" id="mouse_manufacturer">
<property name="halign">center</property>
<property name="justify">center</property>
<property name="label">DPI:</property>
<property name="margin-end">10</property>
<property name="valign">center</property>
<style>
<class name="heading" />
</style>
</object>
</child>
<child>
<object class="GtkSpinButton" id="dpi_spinbutton">
<property name="adjustment">
<object class="GtkAdjustment" id="dpi_adjustment">
<property name="step-increment">100.0</property>
<property name="upper">26000.0</property>
<property name="value">200.0</property>
</object>
</property>
<property name="climb-rate">100.0</property>
<property name="margin-end">10</property>
<property name="margin-start">10</property>
<property name="value">200.0</property>
<property name="width-request">150</property>
<object class="GtkLabel" id="mouse_name">
<property name="halign">center</property>
<property name="justify">center</property>
<style>
<class name="heading" />
</style>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="justify">right</property>
<property name="label">(Maximum: 26000)</property>
<property name="margin-end">10</property>
<property name="valign">center</property>
<object class="AdwPreferencesGroup" id="wireless">
<property name="margin-top">20</property>
<property name="margin-start">20</property>
<property name="margin-end">20</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<property name="css-classes">boxed-list</property>
<child>
<object class="GtkListBoxRow">
<property name="activatable">false</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkLevelBar" id="battery_level">
<property name="value">50</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="margin-start">10</property>
<property name="margin-end">10</property>
<property name="max-value">100</property>
</object>
</child>
<child>
<object class="GtkBox">
<property name="halign">fill</property>
<property name="spacing">10</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkLabel" id="charging_state">
<property name="halign">start</property>
<property name="label">Not charging</property>
<property name="margin-start">10</property>
<property name="margin-bottom">5</property>
</object>
</child>
<child>
<object class="GtkLabel" id="battery_level_label">
<property name="halign">end</property>
<property name="label">50%</property>
<property name="margin-start">120</property>
<property name="margin-end">10</property>
<property name="margin-bottom">5</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox" id="basic_settings">
<property name="halign">start</property>
<property name="margin-top">20</property>
<property name="margin-start">40</property>
<property name="orientation">vertical</property>
<property name="valign">start</property>
<property name="hexpand">false</property>
<child>
<object class="GtkBox" id="dpi_box">
<property name="halign">start</property>
<property name="margin-top">30</property>
<property name="valign">start</property>
<child>
<object class="AdwPreferencesGroup" id="dpi_preference_group">
<property name="height-request">200</property>
<property name="header-suffix">
<object class="GtkButton">
<property name="child">
<object class="AdwButtonContent" id="dpi_stage_add_button">
<property name="icon-name">list-add-symbolic</property>
<property name="label">1/1</property>
<property name="use-underline">True</property>
</object>
</property>
</object>
</property>
<property name="width-request">350</property>
<property name="title">DPI stages</property>
<property name="description">Sensibility stages of the mouse</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-top">35</property>
<child>
<object class="GtkLabel">
<property name="label">Mouse features</property>
<property name="halign">start</property>
<property name="css-classes">heading</property>
</object>
</child>
<child>
<object class="GtkListBoxRow">
<property name="activatable">false</property>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="spacing">10</property>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<property name="css-classes">boxed-list</property>
<property name="margin-top">10</property>
<child>
<object class="AdwSwitchRow" id="motion_sync_switchrow">
<property name="activatable">true</property>
<property name="width-request">200</property>
<property name="hexpand">false</property>
<property name="hexpand-set">true</property>
<property name="title">Motion sync</property>
<property name="subtitle">Motion sync makes the mouse grab sensor data at
the rate of the computer update requests </property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<property name="css-classes">boxed-list</property>
<property name="margin-top">10</property>
<child>
<object class="AdwSwitchRow" id="angle_snap_switchrow">
<property name="activatable">true</property>
<property name="width-request">200</property>
<property name="hexpand">false</property>
<property name="hexpand-set">true</property>
<property name="title">Angle snap</property>
<property name="subtitle">Angle snap detects when you try to draw a straight
line and locks the mouse on that line</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-top">35</property>
<child>
<object class="GtkLabel">
<property name="label">Polling rate</property>
<property name="halign">start</property>
<property name="css-classes">heading</property>
<property name="margin-bottom">10</property>
</object>
</child>
<child>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<property name="css-classes">boxed-list</property>
<property name="margin-top">10</property>
<child>
<object class="AdwComboRow" id="polling_rate_comborow">
<property name="activatable">true</property>
<property name="title">Polling rate</property>
<property name="subtitle">The rate at wich the mouse sends data to the computer</property>
<property name="model">
<object class="GtkStringList">
<items>
<item>125 Hz</item>
<item>250 Hz</item>
<item>500 Hz</item>
<item>1000 Hz</item>
<item>2000 Hz</item>
<item>4000 Hz</item>
</items>
</object>
</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>