@@ -239,6 +239,9 @@ void settings_render_gui(bool *p_open, AppSettings *app_settings, ImFont *roboto
239239 // Co-op error flag (block Apply when host IP/port is invalid)
240240 static bool coop_host_input_error = false ;
241241
242+ // Hotkey duplicate error flag (block Apply when two goals share the same key)
243+ static bool hotkey_duplicate_error = false ;
244+
242245 // Co-op tab state (at function scope so revert/open can reset them)
243246 static char coop_identity_status_msg[256 ] = " " ;
244247 static bool coop_identity_status_is_error = false ;
@@ -3332,6 +3335,7 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
33323335 } // End of Co-op Tab
33333336
33343337 if (ImGui::BeginTabItem (" Hotkeys" )) {
3338+ hotkey_duplicate_error = false ; // Reset each frame; re-evaluated below if counters exist
33353339 ImGui::TextDisabled (
33363340 " Select a template with custom goals using target values different from 0 to adjust their hotkeys here." );
33373341 // --- Hotkey Settings ---
@@ -3457,6 +3461,48 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
34573461 }
34583462 }
34593463 }
3464+
3465+ // --- Duplicate Hotkey Validation ---
3466+ // Collect all active (non-"None") keys and check for duplicates
3467+ for (int i = 0 ; i < temp_settings.hotkey_count && !hotkey_duplicate_error; ++i) {
3468+ const char *inc_i = temp_settings.hotkeys [i].increment_key ;
3469+ const char *dec_i = temp_settings.hotkeys [i].decrement_key ;
3470+ bool inc_active = (strcmp (inc_i, " None" ) != 0 );
3471+ bool dec_active = (strcmp (dec_i, " None" ) != 0 );
3472+
3473+ // Check increment vs decrement within the same binding
3474+ if (inc_active && dec_active && strcmp (inc_i, dec_i) == 0 ) {
3475+ hotkey_duplicate_error = true ;
3476+ break ;
3477+ }
3478+
3479+ // Check against all other bindings
3480+ for (int j = i + 1 ; j < temp_settings.hotkey_count ; ++j) {
3481+ const char *inc_j = temp_settings.hotkeys [j].increment_key ;
3482+ const char *dec_j = temp_settings.hotkeys [j].decrement_key ;
3483+ bool inc_j_active = (strcmp (inc_j, " None" ) != 0 );
3484+ bool dec_j_active = (strcmp (dec_j, " None" ) != 0 );
3485+
3486+ if (inc_active && inc_j_active && strcmp (inc_i, inc_j) == 0 ) {
3487+ hotkey_duplicate_error = true ; break ;
3488+ }
3489+ if (inc_active && dec_j_active && strcmp (inc_i, dec_j) == 0 ) {
3490+ hotkey_duplicate_error = true ; break ;
3491+ }
3492+ if (dec_active && inc_j_active && strcmp (dec_i, inc_j) == 0 ) {
3493+ hotkey_duplicate_error = true ; break ;
3494+ }
3495+ if (dec_active && dec_j_active && strcmp (dec_i, dec_j) == 0 ) {
3496+ hotkey_duplicate_error = true ; break ;
3497+ }
3498+ }
3499+ }
3500+
3501+ if (hotkey_duplicate_error) {
3502+ ImGui::Spacing ();
3503+ ImGui::TextColored (ImVec4 (1 .0f , 0 .3f , 0 .3f , 1 .0f ),
3504+ " Error: Two or more goals share the same hotkey. Each key can only be used once." );
3505+ }
34603506 }
34613507
34623508 ImGui::EndTabItem ();
@@ -3517,7 +3563,7 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
35173563 // Disable "Apply Settings" button on visual editing mode or unsaved template editor changes
35183564 bool visual_editing = t && t->is_visual_layout_editing ;
35193565 bool template_unsaved = t && t->template_editor_has_unsaved_changes ;
3520- bool apply_disabled = visual_editing || template_unsaved || coop_host_input_error;
3566+ bool apply_disabled = visual_editing || template_unsaved || coop_host_input_error || hotkey_duplicate_error ;
35213567
35223568 // Apply the changes or pressing Enter or Ctrl/Cmd + S keys in the settings window when NO popup is shown
35233569
@@ -3648,6 +3694,10 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
36483694 snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
36493695 " Disabled due to an invalid IP address or port in the Co-op tab.\n "
36503696 " Fix the highlighted fields before applying." );
3697+ } else if (hotkey_duplicate_error) {
3698+ snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
3699+ " Disabled because two or more goals share the same hotkey.\n "
3700+ " Each key can only be assigned to one action across all goals." );
36513701 } else {
36523702 snprintf (apply_button_tooltip_buffer, sizeof (apply_button_tooltip_buffer),
36533703 " Apply any changes made in this window. You can also press 'ENTER' or 'Ctrl/Cmd + S' to apply.\n "
@@ -3836,6 +3886,10 @@ ImGui::SetTooltip("%s", tooltip_buffer); \
38363886 snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
38373887 " Disabled due to an invalid IP address or port in the Co-op tab.\n "
38383888 " Fix the highlighted fields before restarting." );
3889+ } else if (hotkey_duplicate_error) {
3890+ snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
3891+ " Disabled because two or more goals share the same hotkey.\n "
3892+ " Each key can only be assigned to one action across all goals." );
38393893 } else {
38403894 snprintf (restart_button_tooltip_buffer, sizeof (restart_button_tooltip_buffer),
38413895 " Saves all current settings and restarts the application.\n "
0 commit comments