From 792dccfe9707c4ea7f400c286f2a7eeb6ee75337 Mon Sep 17 00:00:00 2001
From: dokutan <54861821+dokutan@users.noreply.github.com>
Date: Fri, 13 Dec 2019 03:02:46 +0100
Subject: [PATCH] Add fire button and investigate macro repetition

---
 README.md               | 10 ++++-----
 example.ini             |  6 ++---
 include/constructor.cpp |  2 ++
 include/data.cpp        |  3 +++
 include/getters.cpp     | 10 +++++++++
 include/mouse_m908.h    |  9 ++++++--
 include/print_help.cpp  |  1 +
 include/setters.cpp     | 49 ++++++++++++++++++++++++++++++++++++++++-
 include/writers.cpp     | 21 ++++++++++++++++++
 keymap.md               | 12 ++++++++++
 mouse_m908.cpp          | 18 +++++++++++++++
 11 files changed, 129 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md
index 9167224..807ed11 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
 # mouse_m908
 Control the Redragon M908 Impact gaming mouse from Linux
 
+## Status
+All settings from the official software are implemented, except repeating macros, which seems to be broken in the official software and is therefore currently disabled in this program.
+As a result there will be no changes to this program, unless I overlooked some features or find a bug.
+
 ## Installing
 - Install the dependencies:
   - libusb
@@ -46,7 +50,6 @@ There is space for 15 macros on the mouse, these are shared over all profiles. E
 2. Add macro⟨N⟩ to the button mapping configuration to set a button to the ⟨N⟩th macro
 3. Apply the configuration: mouse_m908 -c ⟨config.ini⟩
 4. Apply the specific macro: mouse_m908 -m ⟨macrofile⟩ -n ⟨N⟩
-
 #### Macro file
 Each line contains an action and a parameter separated by a tab. Supported actions are:
 - down	⟨key⟩
@@ -58,8 +61,3 @@ example.macro for an example, keymap.md section Keyboard keys/Keys for a list of
 - mouse_right
 - mouse_middle
 
-## TODO
-~~Button remapping is not (yet) fully supported: macros and keyboard keys aren't implemented.~~ Macros are currently missing some features:
-- [x] delay between keys
-- [x] mousebuttons
-- [ ] repeat
diff --git a/example.ini b/example.ini
index a6f0433..eded4a5 100644
--- a/example.ini
+++ b/example.ini
@@ -39,7 +39,7 @@ report_rate=500
 button_left=left
 button_right=right
 button_middle=middle
-button_fire=left
+button_fire=fire:mouse_left:5:1
 button_dpi_up=dpi+
 button_dpi_down=dpi-
 scroll_up=scroll_up
@@ -51,8 +51,8 @@ button_4=dpi-cycle
 button_5=report_rate+
 button_6=report_rate-
 button_7=profile_switch
-button_8=a
-button_9=b
+button_8=fire:a:5:1
+button_9=a
 button_10=super_l+shift_l+2
 button_11=ctrl_l+a
 button_12=super_l+3
diff --git a/include/constructor.cpp b/include/constructor.cpp
index d414e22..bd690d7 100644
--- a/include/constructor.cpp
+++ b/include/constructor.cpp
@@ -48,6 +48,7 @@ mouse_m908::mouse_m908(){
 			_keymap_data[i][j][0] = _data_settings_3[35+(20*i)+j][8];
 			_keymap_data[i][j][1] = _data_settings_3[35+(20*i)+j][9];
 			_keymap_data[i][j][2] = _data_settings_3[35+(20*i)+j][10];
+			_keymap_data[i][j][3] = _data_settings_3[35+(20*i)+j][11];
 		}
 	}
 	_report_rates.fill( r_125Hz );
@@ -58,6 +59,7 @@ mouse_m908::mouse_m908(){
 		i[3] = _data_macros_codes[count][1];
 		count++;
 	}
+	_macro_repeat.fill( 0x01 );
 	
 	//name → keycode
 	_keycodes = {
diff --git a/include/data.cpp b/include/data.cpp
index 30d11eb..ab49541 100644
--- a/include/data.cpp
+++ b/include/data.cpp
@@ -236,3 +236,6 @@ uint8_t mouse_m908::_data_macros_codes[15][2] =  {
 	{0xd8, 0x0d},
 	{0xa0, 0x0e},
 	{0x68, 0x0f} };
+
+uint8_t mouse_m908::_data_macros_repeat[16] = 
+	{0x02, 0xf3, 0xa2, 0x00, 0x04, 0x00, 0x00, 0x00, 0x91, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
diff --git a/include/getters.cpp b/include/getters.cpp
index 40dd8b2..810051c 100644
--- a/include/getters.cpp
+++ b/include/getters.cpp
@@ -51,3 +51,13 @@ uint8_t mouse_m908::get_dpi( m908_profile profile, int level ){
 mouse_m908::m908_report_rate mouse_m908::get_report_rate( m908_profile profile ){
 	return _report_rates[profile];
 }
+
+uint8_t mouse_m908::get_macro_repeat( int macro_number ){
+	
+	//check if macro_number is valid
+	if( macro_number < 1 || macro_number > 15 ){
+		return 1;
+	}
+	
+	return _macro_repeat[macro_number];
+}
diff --git a/include/mouse_m908.h b/include/mouse_m908.h
index 1a18b0c..b8df94c 100644
--- a/include/mouse_m908.h
+++ b/include/mouse_m908.h
@@ -71,10 +71,11 @@ class mouse_m908{
 		int set_speed( m908_profile profile, uint8_t speed );
 		int set_dpi_enable( m908_profile profile, int level, bool enabled );
 		int set_dpi( m908_profile profile, int level, uint8_t dpi );
-		int set_key_mapping( m908_profile profile, int key, std::array<uint8_t, 3> mapping );
+		int set_key_mapping( m908_profile profile, int key, std::array<uint8_t, 4> mapping );
 		int set_key_mapping( m908_profile profile, int key, std::string mapping );
 		int set_report_rate( m908_profile profile, m908_report_rate report_rate );
 		int set_macro( int macro_number, std::string file );
+		int set_macro_repeat( int macro_number, uint8_t repeat );
 		
 		//getter functions
 		m908_profile get_profile();
@@ -86,11 +87,13 @@ class mouse_m908{
 		bool get_dpi_enable( m908_profile profile, int level );
 		uint8_t get_dpi( m908_profile profile, int level );
 		m908_report_rate get_report_rate( m908_profile profile );
+		uint8_t get_macro_repeat( int macro_number );
 		
 		//writer functions (apply settings to mouse)
 		int write_profile();
 		int write_settings();
 		int write_macro( int macro_number );
+		int write_macro_repeat( int macro_number );
 		
 		//helper functions
 		int open_mouse();
@@ -115,9 +118,10 @@ class mouse_m908{
 		std::array<uint8_t, 5> _speed_levels;
 		std::array<std::array<bool, 5>, 5> _dpi_enabled;
 		std::array<std::array<uint8_t, 5>, 5> _dpi_levels;
-		std::array<std::array<std::array<uint8_t, 3>, 20>, 5> _keymap_data;
+		std::array<std::array<std::array<uint8_t, 4>, 20>, 5> _keymap_data;
 		std::array<m908_report_rate, 5> _report_rates;
 		std::array<std::array<uint8_t, 256>, 15> _macro_data;
+		std::array<uint8_t, 15> _macro_repeat;
 		
 		//setting min and max values
 		uint8_t _scrollspeed_min, _scrollspeed_max;
@@ -140,6 +144,7 @@ class mouse_m908{
 		static uint8_t _data_macros_2[256];
 		static uint8_t _data_macros_3[16];
 		static uint8_t _data_macros_codes[15][2];
+		static uint8_t _data_macros_repeat[16];
 };
 
 #include "data.cpp"
diff --git a/include/print_help.cpp b/include/print_help.cpp
index 1114b6a..d85ab87 100644
--- a/include/print_help.cpp
+++ b/include/print_help.cpp
@@ -26,4 +26,5 @@ void print_help(){
 	std::cout << "-p --profile\n\tSets currently active profile (1-5).\n";
 	std::cout << "-m --macro\n\tSelects macro file for sending.\n";
 	std::cout << "-n --number\n\tSelects macro slot for sending (1-15).\n";
+	//std::cout << "-r --repeat\n\tSets number of times the macro will be repeated (1-255).\n";
 }
diff --git a/include/setters.cpp b/include/setters.cpp
index 6ffafe9..f155816 100644
--- a/include/setters.cpp
+++ b/include/setters.cpp
@@ -89,10 +89,11 @@ int mouse_m908::set_dpi( m908_profile profile, int level, uint8_t dpi ){
 	return 0;
 }
 
-int mouse_m908::set_key_mapping( m908_profile profile, int key, std::array<uint8_t, 3> mapping ){
+int mouse_m908::set_key_mapping( m908_profile profile, int key, std::array<uint8_t, 4> mapping ){
 	_keymap_data[profile][key][0] = mapping[0];
 	_keymap_data[profile][key][1] = mapping[1];
 	_keymap_data[profile][key][2] = mapping[2];
+	_keymap_data[profile][key][3] = mapping[3];
 	return 0;
 }
 
@@ -103,6 +104,40 @@ int mouse_m908::set_key_mapping( m908_profile profile, int key, std::string mapp
 		_keymap_data[profile][key][0] = _keycodes[mapping][0];
 		_keymap_data[profile][key][1] = _keycodes[mapping][1];
 		_keymap_data[profile][key][2] = _keycodes[mapping][2];
+		_keymap_data[profile][key][3] = 0x00;
+	} else if( mapping.find("fire") == 0 ){
+		// fire button (multiple keypresses)
+		
+		std::stringstream mapping_stream(mapping);
+		std::string value1 = "", value2 = "", value3 = "";
+		uint8_t keycode, repeats = 1, delay = 0;
+		
+		std::getline( mapping_stream, value1, ':' );
+		std::getline( mapping_stream, value1, ':' );
+		std::getline( mapping_stream, value2, ':' );
+		std::getline( mapping_stream, value3, ':' );
+		
+		if( value1 == "mouse_left" ){
+			keycode = 0x81;
+		} else if( value1 == "mouse_right" ){
+			keycode = 0x82;
+		} else if( value1 == "mouse_middle" ){
+			keycode = 0x84;
+		} else if( _keyboard_key_values.find(value1) != _keyboard_key_values.end() ){
+			keycode = _keyboard_key_values[value1];
+		} else{
+			return 1;
+		}
+		
+		repeats = (uint8_t)stoi(value2);
+		delay = (uint8_t)stoi(value3);
+		
+		// store values
+		_keymap_data[profile][key][0] = 0x99;
+		_keymap_data[profile][key][1] = keycode;
+		_keymap_data[profile][key][2] = repeats;
+		_keymap_data[profile][key][3] = delay;
+		
 	} else{
 		// string is not a key in _keycodes: keyboard key?
 		
@@ -123,6 +158,7 @@ int mouse_m908::set_key_mapping( m908_profile profile, int key, std::string mapp
 			_keymap_data[profile][key][0] = first_value;
 			_keymap_data[profile][key][1] = modifier_value;
 			_keymap_data[profile][key][2] = _keyboard_key_values[std::regex_replace( mapping, modifier_regex, "" )];
+			_keymap_data[profile][key][3] = 0x00;
 			//std::cout << std::regex_replace( mapping, modifier_regex, "" ) << "\n";
 		} catch( std::exception& f ){
 			return 1;
@@ -230,3 +266,14 @@ int mouse_m908::set_macro( int macro_number, std::string file ){
 	
 	return 0;
 }
+
+int mouse_m908::set_macro_repeat( int macro_number, uint8_t repeat ){
+	
+	//check if macro_number is valid
+	if( macro_number < 1 || macro_number > 15 ){
+		return 1;
+	}
+	
+	_macro_repeat[macro_number] = repeat;
+	return 0;
+}
diff --git a/include/writers.cpp b/include/writers.cpp
index c64138b..89d83d6 100644
--- a/include/writers.cpp
+++ b/include/writers.cpp
@@ -127,6 +127,8 @@ int mouse_m908::write_settings(){
 			buffer3[35+(20*i)+j][8] = _keymap_data[i][j][0];
 			buffer3[35+(20*i)+j][9] = _keymap_data[i][j][1];
 			buffer3[35+(20*i)+j][10] = _keymap_data[i][j][2];
+			buffer3[35+(20*i)+j][11] = _keymap_data[i][j][3];
+			//std::cout << (int)_keymap_data[i][j][0] << " " << (int)_keymap_data[i][j][1] << " " << (int)_keymap_data[i][j][2] << " " << (int)_keymap_data[i][j][3] << "\n";
 		}
 	}
 	//usb report rate
@@ -204,3 +206,22 @@ int mouse_m908::write_macro( int macro_number ){
 	
 	return 0;
 }
+
+int mouse_m908::write_macro_repeat( int macro_number ){
+	
+	//check if macro_number is valid
+	if( macro_number < 1 || macro_number > 15 ){
+		return 1;
+	}
+	
+	//prepare data
+	uint8_t buffer[16];
+	std::copy(std::begin(_data_macros_repeat), std::end(_data_macros_repeat), std::begin(buffer));
+	
+	buffer[10] = _macro_repeat[macro_number];
+	
+	//send data
+	libusb_control_transfer( _handle, 0x21, 0x09, 0x0302, 0x0002, buffer, 16, 1000 );
+	
+	return 0;
+}
diff --git a/keymap.md b/keymap.md
index a6bb3eb..1609718 100644
--- a/keymap.md
+++ b/keymap.md
@@ -1,6 +1,18 @@
 # keymap.md
 This documents all options for button mapping.
 
+## Fire button (simulates multiple nutton presses)
+fire:⟨button⟩:⟨repeats⟩:⟨delay⟩
+
+⟨button⟩ can be:
+- mouse_left
+- mouse_right
+- mouse_middle
+- a keyboard key
+
+⟨repeats⟩ can be 1-255
+⟨delay⟩ can be 1-255
+
 ## Mousebuttons and special functions
 forward
 backward
diff --git a/mouse_m908.cpp b/mouse_m908.cpp
index 293d95a..92f354a 100644
--- a/mouse_m908.cpp
+++ b/mouse_m908.cpp
@@ -41,16 +41,20 @@ int main( int argc, char **argv ){
 		{"profile", required_argument, 0, 'p'},
 		{"macro", required_argument, 0, 'm'},
 		{"number", required_argument, 0, 'n'},
+		//{"repeat", required_argument, 0, 'r'},
 		{0, 0, 0, 0}
 	};
 	
 	bool flag_config = false, flag_profile = false;
 	bool flag_macro = false, flag_number = false;
+	//bool flag_repeat;
 	std::string string_config, string_profile;
 	std::string string_macro, string_number;
+	//std::string string_repeat;
 	
 	//parse command line options
 	int c, option_index = 0;
+	//while( (c = getopt_long( argc, argv, "hc:p:m:n:r:",
 	while( (c = getopt_long( argc, argv, "hc:p:m:n:",
 	long_options, &option_index ) ) != -1 ){
 		
@@ -75,6 +79,10 @@ int main( int argc, char **argv ){
 				flag_number = true;
 				string_number = optarg;
 				break;
+			//case 'r':
+			//	flag_repeat = true;
+			//	string_repeat = optarg;
+			//	break;
 			case '?':
 				break;
 			default:
@@ -438,6 +446,16 @@ int main( int argc, char **argv ){
 		
 		m.write_macro(number);
 		
+		/*if( flag_repeat ){
+			int repeat = (int)stoi(string_repeat);
+			r = m.set_macro_repeat( number, repeat );
+			if( r != 0 ){
+				std::cout << "Invalid repeats\n";
+				return 1;
+			}
+			m.write_macro_repeat( number );
+		}*/
+		
 		m.close_mouse();
 		
 	} else if( flag_macro || flag_number ){