Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

## [Unreleased]

### Добавлено
- **Края тачпада работают как ползунки** — провести пальцем вверх-вниз вдоль левого края меняет
яркость, вдоль правого громкость, как в родном софте производителя. Курсор в этих полосах не
двигается: используется та же штатная зона Windows, что и у мёртвой зоны снизу, а сам жест
читается сырым вводом. Что одно другому не мешает — измерено, а не предположено. Движение,
начатое в центре панели, спокойно продолжается в полосы и ползунком не становится: одним
движением делается что-то одно. Двумя пальцами по-прежнему прокрутка. Чувствительность
выбирается тремя пресетами, причём яркость и громкость идут в ногу — раньше такие шкалы
разъезжались бы, потому что шаг громкости в Windows равен двум процентам, а не пяти.
Выключено по умолчанию, нужен Precision Touchpad (XIC-61).

## [0.13.0] — 2026-08-31

### Добавлено
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,12 @@ rate is set with `ChangeDisplaySettingsEx` — no driver needed):
alias) cycles the built-in panel's available modes and shows the resulting rate in the localized
OSD. With **Keep refresh rate** enabled, that deliberate choice becomes the configured rate for
the current power source, so the guard does not undo it 1.5 seconds later.
- **Touchpad edges as sliders.** Slide a finger up and down along the left edge for brightness,
along the right edge for volume (Settings → Touchpad). The pointer does not move inside those
strips: it uses the same stock Windows curtain zone as the bottom dead zone. A movement that
began in the middle of the pad continues into the strips as usual and never becomes a slider —
one gesture does one thing. Two fingers still scroll. Sensitivity has three presets, and
brightness and volume move in step. Requires a Windows Precision Touchpad.
- **No such key on your model?** The same cycle is available as the assignable action `hz`
("Cycle screen refresh rate") in Settings → Keys, so it can be put on the Mi button or any other
remappable key. It obeys the same **Refresh rate control** toggle: with the feature off, nothing
Expand Down
6 changes: 6 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,12 @@ dotnet publish src/XiControl.csproj -c Release -r win-x64 --self-contained -p:Pu
перебирает доступные режимы встроенной панели и показывает итоговую частоту в локализованном
OSD. При включённом **«Удерживать частоту»** этот осознанный выбор становится значением профиля
текущего питания, поэтому guard не отменяет его через 1,5 секунды.
- **Края тачпада как ползунки.** Провести пальцем вверх-вниз вдоль левого края — яркость,
вдоль правого — громкость (Настройки → Тачпад). Курсор в этих полосах не двигается: используется
та же штатная curtain-зона Windows, что и у мёртвой зоны снизу. Движение, начатое в центре
панели, продолжается в полосы как обычно и ползунком не становится — одним движением делается
что-то одно. Двумя пальцами по-прежнему прокрутка. Чувствительность — три пресета, яркость и
громкость идут в ногу. Нужен Precision Touchpad.
- **На твоей модели такой клавиши нет?** Тот же цикл доступен как назначаемое действие `hz`
(«Частота экрана по кругу») в Настройки → Клавиши — вешается на Mi-кнопку или любую другую
переназначаемую клавишу. Подчиняется тому же тумблеру **«Управление частотой»**: фича выключена —
Expand Down
17 changes: 17 additions & 0 deletions src/Config/AppConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,23 @@ public sealed class AppConfig
/// только по явному выбору пользователя.</summary>
public bool RememberBrightness { get; set; } = false;

/// <summary>Края тачпада работают как ползунки: слева яркость, справа громкость (XIC-61).
/// Курсорное движение в полосах гасится штатными curtain-зонами Windows, сам жест читается
/// через Raw Input. Требует Precision Touchpad; выключено по умолчанию.</summary>
public bool TouchpadEdgeSliders { get; set; } = false;

/// <summary>Ширина краевой полосы, мм. Потолок не косметический: измерено, что заведомо
/// большое значение PTP-маппер игнорирует вовсе и зона просто не работает.</summary>
public int TouchpadEdgeWidthMm { get; set; } = SystemIntegration.TouchpadEdgeSliders.DefaultWidthMm;

/// <summary>Чувствительность краевых ползунков: сколько проходов вдоль края нужно, чтобы
/// пройти шкалу целиком. Один — резко, три — плавно. Яркость и громкость идут по этой
/// величине в ногу; до неё они расходились в 2,5 раза (шаг громкости Windows — 2%).</summary>
public int TouchpadEdgeSwipesPerRange { get; set; } = 2;

/// <summary>Поменять края местами: слева громкость, справа яркость.</summary>
public bool TouchpadEdgeSwap { get; set; } = false;

/// <summary>
/// Отладка: читать температуру из ACPI-термозоны, даже если Intel DPTF доступен (XIC-41).
/// Только правкой config.json — на Intel-машине путь фолбэка иначе не прогнать, он работает
Expand Down
12 changes: 12 additions & 0 deletions src/Localization/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
"settings.touchpad.deadzone.unit": "mm",
"settings.touchpad.deadzone.note": "Only the start of a touch is suppressed: a finger first placed in the strip doesn't move the cursor and doesn't tap. A gesture started higher up keeps working all the way down, and pressing inside the zone still clicks.",
"settings.touchpad.deadzone.aap": "The zone is inactive right now: touchpad sensitivity in Windows settings is set to maximum, which turns palm rejection off entirely. Pick a lower sensitivity.",
"settings.touchpad.edges": "Edges as sliders",
"settings.touchpad.edges.desc": "Slide a finger up and down along an edge: brightness on the left, volume on the right.",
"settings.touchpad.edges.size": "Strip width",
"settings.touchpad.edges.size.desc": "Measured from the left and right edges of the pad. Requires the option above.",
"settings.touchpad.edges.speed": "Sensitivity",
"settings.touchpad.edges.speed.desc": "How much brightness and volume change per stroke along an edge. Both scales move in step.",
"settings.touchpad.edges.speed.1": "Fast",
"settings.touchpad.edges.speed.2": "Normal",
"settings.touchpad.edges.speed.3": "Smooth",
"settings.touchpad.edges.swap": "Swap the edges",
"settings.touchpad.edges.swap.desc": "Volume on the left, brightness on the right.",
"settings.touchpad.edges.note": "Only the start of a contact is suppressed: a finger placed directly into a strip does not move the pointer. A movement that began in the central part of the pad continues into the side strips as usual — the pointer does not hit an invisible wall. Such a movement never becomes a slider: one gesture does one thing. Two fingers still scroll.",
"settings.updates.check": "Check for updates",
"settings.updates.check.desc": "Ask GitHub once a day whether a new version is out. When off, the app makes no network requests at all. Updates are never installed automatically — we only tell you.",
"settings.updates.available": "Version {0} is available",
Expand Down
12 changes: 12 additions & 0 deletions src/Localization/lang/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
"settings.touchpad.deadzone.unit": "мм",
"settings.touchpad.deadzone.note": "Гасится только начало касания: если палец коснулся панели сразу в этой полосе, курсор не сдвинется и тапа не будет. Жест, начатый выше, работает до самого низа, а нажатие в зоне по-прежнему срабатывает.",
"settings.touchpad.deadzone.aap": "Сейчас зона не работает: в параметрах Windows для тачпада выбрана максимальная чувствительность — она отключает защиту от ладони целиком. Выберите чувствительность пониже.",
"settings.touchpad.edges": "Края как ползунки",
"settings.touchpad.edges.desc": "Провести пальцем вверх-вниз вдоль края: слева яркость, справа громкость.",
"settings.touchpad.edges.size": "Ширина полос",
"settings.touchpad.edges.size.desc": "Отсчитывается от левого и правого края панели. Нужен включённый пункт выше.",
"settings.touchpad.edges.speed": "Чувствительность",
"settings.touchpad.edges.speed.desc": "Насколько сильно меняются яркость и громкость за одно движение вдоль края. Обе шкалы идут в ногу.",
"settings.touchpad.edges.speed.1": "Резко",
"settings.touchpad.edges.speed.2": "Обычно",
"settings.touchpad.edges.speed.3": "Плавно",
"settings.touchpad.edges.swap": "Поменять края местами",
"settings.touchpad.edges.swap.desc": "Слева станет громкость, справа яркость.",
"settings.touchpad.edges.note": "Гасится только начало касания: палец, опущенный сразу в полосу, курсор не двигает. Движение, начатое в центральной части панели, спокойно продолжается в боковые зоны — курсор не упирается в невидимую стену. Ползунком такое движение не становится: одним движением делается что-то одно. Двумя пальцами по-прежнему прокрутка.",
"settings.updates.check": "Проверять обновления",
"settings.updates.check.desc": "Раз в сутки спрашивать у GitHub, вышла ли новая версия. Выключено — приложение не обращается в сеть вообще. Обновление не устанавливается само: мы только сообщаем.",
"settings.updates.available": "Доступна версия {0}",
Expand Down
12 changes: 12 additions & 0 deletions src/Localization/lang/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@
"settings.touchpad.deadzone.unit": "毫米",
"settings.touchpad.deadzone.note": "仅抑制触摸的开始:手指首次落在该区域时不会移动光标,也不会触发轻点。从上方开始的手势可以一直滑到底部,在该区域按压仍然可以点击。",
"settings.touchpad.deadzone.aap": "死区当前无效:Windows 设置中触摸板灵敏度为最高,这会完全关闭手掌防误触。请选择较低的灵敏度。",
"settings.touchpad.edges": "边缘作为滑块",
"settings.touchpad.edges.desc": "沿边缘上下滑动手指:左侧调亮度,右侧调音量。",
"settings.touchpad.edges.size": "边缘条宽度",
"settings.touchpad.edges.size.desc": "从触控板左右边缘算起。需先启用上方选项。",
"settings.touchpad.edges.speed": "灵敏度",
"settings.touchpad.edges.speed.desc": "沿边缘滑动一次时亮度与音量的变化幅度。两者保持一致。",
"settings.touchpad.edges.speed.1": "快速",
"settings.touchpad.edges.speed.2": "标准",
"settings.touchpad.edges.speed.3": "平缓",
"settings.touchpad.edges.swap": "左右互换",
"settings.touchpad.edges.swap.desc": "左侧调音量,右侧调亮度。",
"settings.touchpad.edges.note": "仅抑制触摸的起始:手指直接落在边缘条内时不会移动指针。从触控板中间区域开始的移动会照常延伸到边缘条 — 指针不会撞上看不见的墙。这样的移动不会变成滑块:一次动作只做一件事。双指滚动不受影响。",
"settings.updates.check": "检查更新",
"settings.updates.check.desc": "每天向 GitHub 查询一次是否有新版本。关闭后,应用完全不会发起任何网络请求。更新不会自动安装——我们只做提醒。",
"settings.updates.available": "有可用版本 {0}",
Expand Down
1 change: 1 addition & 0 deletions src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private static void Main()
services.AddSingleton<TouchpadControl>();
services.AddSingleton<TouchscreenControl>();
services.AddSingleton<TouchpadDeadZone>();
services.AddSingleton<TouchpadEdgeSliders>();
// «В дорогу» временно снимает защиту (заряд до 100%) — гард бережёт 80% только когда travel выключен
services.AddSingleton(sp =>
{
Expand Down
17 changes: 17 additions & 0 deletions src/SystemIntegration/Brightness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,23 @@ public static void Apply(int percent)
});
}

/// <summary>
/// Установить яркость КАК ПРАВКУ ЧЕЛОВЕКА — без метки Own (XIC-61, краевой ползунок).
/// Разница принципиальная: помеченную запись авто-яркость и лимит считают своей и
/// игнорируют, а ползунок под пальцем — это выбор пользователя, кривая обязана на нём
/// учиться, а лимит — принять его за осознанный. Иначе яркость уезжала бы обратно через
/// минуту, и виноваты были бы мы.
/// </summary>
public static void ApplyAsUser(int percent)
{
int lvl = Math.Clamp(percent, 0, 100);
Task.Run(() =>
{
try { if (Get() != lvl) Set(lvl); }
catch (Exception ex) { Log.Ex("Brightness.ApplyAsUser", ex); }
});
}

/// <summary>
/// Плавный ход от <paramref name="from"/> к <paramref name="to"/> шагами по 1% (шкала
/// непрерывная, проверено на TM2424). Весь путь занимает ~<paramref name="durationMs"/>
Expand Down
65 changes: 65 additions & 0 deletions src/SystemIntegration/EdgeSlideScale.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace XiControl.SystemIntegration;

/// <summary>
/// Пересчёт шагов жеста в проценты яркости и нажатия клавиш громкости (XIC-61) — чистая логика
/// под тестами.
///
/// <b>Зачем вообще отдельный класс.</b> Первая версия двигала яркость на 5% за шаг, а громкость
/// на одно нажатие <c>VK_VOLUME_UP</c>, то есть на 2% — и шкалы разъехались в два с половиной
/// раза: яркость проходилась за пару проходов вдоль края, громкость за четыре. Обе величины
/// теперь измеряются в ОДНИХ единицах (процент своей шкалы), а расхождение по громкости
/// добирается накоплением: два процента дают одно нажатие, остаток переносится.
///
/// Чувствительность задаётся тем, что человек и так чувствует пальцем: <b>сколько проходов
/// вдоль края нужно, чтобы пройти шкалу целиком</b>. Один — резко, три — плавно.
/// </summary>
public sealed class EdgeSlideScale(int swipesPerRange)
{
/// <summary>Доля высоты панели на один шаг жеста. Мелкая нарочно: шкала должна ощущаться
/// непрерывной, а крупность движения задаётся чувствительностью, а не размером шага.</summary>
public const double StepFraction = 0.02;

/// <summary>Шагов в одном полном проходе вдоль края (сверху вниз).</summary>
public const int StepsPerSwipe = (int)(1 / StepFraction);

/// <summary>Шаг громкости Windows по <c>VK_VOLUME_UP</c> — 2% шкалы. Отсюда и пересчёт
/// процентов в нажатия: дробить мельче нечем.</summary>
private const double VolumeTapPercent = 2.0;

/// <summary>Пресеты для настроек: сколько проходов на всю шкалу.</summary>
public static readonly int[] Presets = [1, 2, 3];

/// <summary>Клэмп: config.json правится руками, а ноль проходов — деление на ноль.
/// Верх совпадает с набором пресетов намеренно: иначе вписанное руками значение не имело
/// бы подписи, и комбо в настройках показывало бы не то, что реально работает.</summary>
public static int Normalize(int swipes) => Math.Clamp(swipes, Presets[0], Presets[^1]);

private readonly double _perStep = 100.0 / Normalize(swipesPerRange) / StepsPerSwipe;
private double _brightness;
private double _volume;

/// <summary>Процент своей шкалы на один шаг жеста — для диагностики и тестов.</summary>
public double PercentPerStep => _perStep;

/// <summary>Целые проценты яркости из шагов; дробный остаток переносится в следующий вызов,
/// иначе медленное движение не давало бы ничего вовсе.</summary>
public int Brightness(int steps) => Take(ref _brightness, steps, 1.0);

/// <summary>Число нажатий клавиши громкости: 2% шкалы за нажатие, остаток переносится.</summary>
public int VolumeTaps(int steps) => Take(ref _volume, steps, VolumeTapPercent);

/// <summary>Забыть накопленное — палец оторван, новый жест считает с нуля.</summary>
public void Reset()
{
_brightness = 0;
_volume = 0;
}

private int Take(ref double acc, int steps, double unit)
{
acc += steps * _perStep;
int whole = (int)(acc / unit);
acc -= whole * unit;
return whole;
}
}
Loading
Loading