С Новым годом! Форум программистов, компьютерный форум, киберфорум
C/C++: WinAPI
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 5.00/9: Рейтинг темы: голосов - 9, средняя оценка - 5.00
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126

Перехват клавиатуры Win32API

03.11.2018, 18:48. Показов 1858. Ответов 3

Студворк — интернет-сервис помощи студентам
Вообщем требуется решить такую задачу. Моя программа(Автокликер) должна начать перехватывать клавиатуру после нажатия горячей комбинации Ctrl+Shift+Alt+Z(будет обрабатывать цифры 0-9 и клавишу Enter, ПРИЧЁМ ГЛОБАЛЬНО, ТО ЕСТЬ НЕ ЗАВИСИМО В ФОКУСЕ МОЯ ПРОГРАММА ИЛИ НЕТ!), это нужно для того чтобы считать новый интервал кликов (ну чтобы настройки каждый раз не открывать), обработка и создание такой горячей клавиши есть, вызываю RegisterHotKey, а в окне ловлю сообщение WM_HOTKEY, с этим всё понятно, с этим проблем нет. А вот с перехватом клавиатуры у меня проблемы, я делаю это с помощью хуков, регистрирую хук вот так:
C++
1
PP.hHookReadMiliSecondsOfClicks = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hInst, NULL);
PP.hHookReadMiliSecondsOfClicks - Имеет тип данных HHOOK
PP определено вот так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct ProgrammParameters {
    HWND GlobalHWND = NULL;
    bool GlobalLeftButtonClick = true; // щёлкать левой кнопкой мыши если выбран переключатель IDC_RADIO_LEFT_BUTTON_CLICK в IDD_SETTINGS 
    bool GlobalRightButtonClick = false;// щёлкать правой кнопкой мыши если выбран переключатель IDC_RADIO_RIGHT_BUTTON_CLICK в IDD_SETTINGS 
    bool ClickMouse = false; // эта переменная становиться true если нажато Ctrl + Alt + Shift + C, и false при повторном нажатии и отвечает за то, будут ли происходить щелчки мышью или нет
    LRESULT GlobalMiliSecondsOfClicks = 10; // глобальная переменная которая содержит интервал кликов
    static enum TimerIds {
        TimerClickId
    }; // в этом перечислении сожержаться идентификаторы таймеров, идентификатр таймера TimerClickId связан с таймером который отправляет сообщение WM_TIMER в соотвествии с интервалом GlobalMiliSecondsOfClicks
    bool TheApplicationHasATrayIcon = false;// эта переменная равна true если приложение имеет иконку в трее
    WORD MaxMiliSeconds = 1000;// максимальное значение интервала кликов
    HHOOK hHookReadMiliSecondsOfClicks = NULL;
    bool HookReadMilisecondsOfClicksIsRegistred = false;
} PP;
- глобальная структура, в самом верху файла Autoclicker.cpp(главный файл проекта)
KeyboardProc - это KeyBoardProc определена вот так:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, LPARAM lParam) {
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code < 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
    else if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        break;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        break;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        break;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        break;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        break;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        break;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        break;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        break;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        break;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        break;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            UpdateWindow(PP.GlobalHWND);
                        }
                        break;
                    }
                    
                }
                break;
        }
    }
}
hInst - HINSTANCE моего приложения.
Но этот код работает, но не совсем так как хотелось, например когда пользователь вводит 0, выводиться сообщение об ошибке(так и должно быть), но оно тут же закрывается как будто я нажал на Enter (так не должно быть).
Ещё раз моя последовательность действий:
1. Запускаю программу.
2. нажимаю Ctrl+Shift+Alt+Z
3. Ввожу 0.
4. Получаю сообщение об ошибке которое тут же закрывается, как будто я нажал на Enter(хотя так быть не должно, оно должно появиться и не исчезнуть, пока я что-нибудь не сделаю)
Я так подозреваю что проблема в KeyboardProc, возможно сообщения остаются в очереди, но такого быть не должно моя программа должна перехватывать клавиши, обрабатывать их нажатие, а остальные программы должны думать что нажатий вообще не было, то есть она должна их "съедать".
Вот ссылка на репозиторий на GitHub с полным проектом
Пожалуйста помогите. Проект создавался в Visual Studio 2017, написан на C++ и WinAPI.(смотреть в файл Autoclicker.cpp)

Добавлено через 2 часа 23 минуты
Первую проблему я решил, функция KeyboardProc должна иметь такой вид:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, LPARAM lParam) {
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        return TRUE;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        return TRUE;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        return TRUE;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        return TRUE;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        return TRUE;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        return TRUE;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        return TRUE;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        return TRUE;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        return TRUE;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        return TRUE;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            UpdateWindow(PP.GlobalHWND);
                        }
                        return TRUE;
                    }
                    
                }
                break;
        }
    
    }
    else if (code > 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
}
Но возникла другая проблема, вот в чём она заключается:
При нажатии Ctrl+Shift+Alt+Z, Ctrl+Alt+Shift остаётся нажатой даже после отжатия Ctrl+Shift+Alt+Z, как это проявляется, ну если нажать на иконку любого приложения на панели задач, то оно запускаеться от администратора, а если нажать Ctrl+Alt+Shift до первого действия, то эффект пропадает. Как это исправить?
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
03.11.2018, 18:48
Ответы с готовыми решениями:

Перехват нажатий клавиатуры
Добрый день! Мы вообще то пишем на java, но необходим кусочек кода на С++, который бы вызывал основную программу. Пока задача минимальна:...

Глобальный перехват события клавиатуры
Использую пример создания хука для глобального перехвата сообщений http://www.rsdn.ru/article/baseserv/hookdll.xml Пример рабочий...

Реализовать перехват событий клавиатуры (хуки)
Здравствуйте Уважаемые форумчане.Несколько дней бьюсь с проблемой,и решил попросить Вас помочь с ее решением. Проблема следующая:Я сделал...

3
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126
09.01.2019, 19:56  [ТС]
В ходе моих исследований я выяснил:
1. Клавиши залипают тогда, когда хук на перехват клавиатуры регистрируется в момент когда клавиши нажаты физически, ну то есть, если быстро нажать Ctrl+Alt+Shift+Z, то требуемый функционал срабатывает и клавиши не залипают, из этого можно сделать вывод, для того чтобы это исправить следует добавить проверку нажаты ли клавиши физически, если нажаты то ждать пока отпустят, если отпущены то продолжать.
Вот пример исправленного фрагмента кода(здесь представлена обработка горячей клавиши Ctrl+Alt+Shift+Z):
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
case HotKeys::HotKeySetIntervalOfClicksId:
                    // данный участок кода предоствращает баг с залипанием клавиш Ctrl, Alt, Shift
                    LabelBeginForCheckKeyStates:
                    if (CheckBit(GetAsyncKeyState(VK_CONTROL), 16) == true || CheckBit(GetAsyncKeyState(VK_SHIFT), 16) == true || CheckBit(GetAsyncKeyState(VK_MENU), 16)) {// данное условие проверяет нажата какая-либо из клавиш Ctrl, Alt, Shift
                        Sleep(500);
                        goto LabelBeginForCheckKeyStates;
                    }
                    else {
                        goto LabelEndForCheckKeyStates;
                    }
                    LabelEndForCheckKeyStates:
                    if (PP.HookReadMilisecondsOfClicksIsRegistred == true) {
                        UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                        return (LRESULT)FALSE;
                    }
                    //конец данного участка
                    PP.hHookReadMiliSecondsOfClicks = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, hInst, NULL);
                    if (PP.hHookReadMiliSecondsOfClicks == NULL) {
                        DWORD RHKError = GetLastError();
                        LPTSTR BufferForFormatMessage = nullptr;
                        DWORD FMResult = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, RHKError, LANG_SYSTEM_DEFAULT, (LPTSTR)&BufferForFormatMessage, NULL, nullptr);
                        if (FMResult == 0) {
                            MessageBox(hWnd, _TEXT("Не удалось выполнить функцию SetWindowsHookEx!"), _TEXT("Ошибка при выполнении функции SetWindowsHookEx!"), MB_OK | MB_ICONERROR);
                            MessageBox(hWnd, _TEXT("Ошибка! Не удалось узнать причину возникновения ошибки при выполнении функции SetWindowsHookEx!"), _TEXT("Мне не удалось причину возникновения ошибки!"), MB_OK | MB_ICONERROR);
                                    
                        }
                        else {
                            MessageBox(hWnd, BufferForFormatMessage, _TEXT("Не удалось выполнить функцию SetWindowsHookEx!"), MB_OK | MB_ICONERROR);
                        }
                        if (LocalFree((HLOCAL)BufferForFormatMessage) != 0) {
                            MessageBox(hWnd, _TEXT("Не удалось освободить буфуер при обработке ошибки выполнения функции SetWindowsHookEx!"), _TEXT("Ошибка!"), MB_OK | MB_ICONERROR);
                        }
                    }
                    else {
                        
                    }
                    return (LRESULT)TRUE;
2. Баг с закрытием MessageBox при неверном вводе числа я таки не исправил, для того чтобы его исправить нужно перестроить логику KeyboardProc, в дальнейшем я приведу пример с исправлеными багами.
Почему же MessageBox закрывался сам? Да всё просто, если проверить, то он закрывается только тогда, когда вы отпускаете клавишу Enter, в KeyboardProc я перехватывал только нажатие клавиш, в том числе и Enter, если же перехватывать оба сообщения и перестроить логику таким образом, чтобы основные действия выполнялись когда пользователь отпускает клавишу, то ошибка пропадает, вот пример исправленной KeyboardProc:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
LRESULT CALLBACK KeyboardProc(_In_ int code, _In_ WPARAM wParam, _In_ LPARAM lParam) {// функция для перехвата клавиатуры, перехват будет происходить при зажатии Ctrl+Alt+Shift+Z, будут перехватываться клавиши с цифрами 0-9, это нужно для того чтобы ввести новый интервал кликов не открывая настройки автокликера
    static TSTRING StringValueMilisecondsOfClick;
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *)lParam;
    if (code == HC_ACTION) {
        switch (wParam) {
            case WM_KEYUP:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                        StringValueMilisecondsOfClick += _TEXT('0');
                        return TRUE;
                    case 0x61:
                    case 0x31:
                        StringValueMilisecondsOfClick += _TEXT('1');
                        return TRUE;
                    case 0x62:
                    case 0x32:
                        StringValueMilisecondsOfClick += _TEXT('2');
                        return TRUE;
                    case 0x63:
                    case 0x33: 
                        StringValueMilisecondsOfClick += _TEXT('3');
                        return TRUE;
                    case 0x64:
                    case 0x34:
                        StringValueMilisecondsOfClick += _TEXT('4');
                        return TRUE;
                    case 0x65:
                    case 0x35: 
                        StringValueMilisecondsOfClick += _TEXT('5');
                        return TRUE;
                    case 0x66:
                    case 0x36:
                        StringValueMilisecondsOfClick += _TEXT('6');
                        return TRUE;
                    case 0x67:
                    case 0x37:
                        StringValueMilisecondsOfClick += _TEXT('7');
                        return TRUE;
                    case 0x68:
                    case 0x38: 
                        StringValueMilisecondsOfClick += _TEXT('8');
                        return TRUE;
                    case 0x69:
                    case 0x39:
                        StringValueMilisecondsOfClick += _TEXT('9');
                        return TRUE;
                    case VK_BACK:
                        if (StringValueMilisecondsOfClick.size() > 0) {
                            StringValueMilisecondsOfClick.pop_back();
                        }
                        return TRUE;
                    case VK_RETURN:{
                        int MilisecondsOfClicks = _ttoi(StringValueMilisecondsOfClick.c_str());
                        if (MilisecondsOfClicks < MIN_MILISECONDS || MilisecondsOfClicks > PP.MaxMiliSeconds) {
                            TCHAR ErrorText[100];
                            wsprintf(ErrorText, _TEXT("Введённое вами число должно быть больше либо равно чем %u и меньше либо равно чем %u"), MIN_MILISECONDS, PP.MaxMiliSeconds);
                            MessageBox(PP.GlobalHWND, ErrorText, _TEXT("Ошибка! Неверное число"), MB_OK | MB_ICONERROR);
                            StringValueMilisecondsOfClick.clear();
                        }
                        else {
                            UnhookWindowsHookEx(PP.hHookReadMiliSecondsOfClicks);
                            PP.GlobalMiliSecondsOfClicks = MilisecondsOfClicks;
                            PP.HookReadMilisecondsOfClicksIsRegistred = false;
                            StringValueMilisecondsOfClick.clear();
                            InvalidateRect(PP.GlobalHWND, NULL, TRUE);
                            
                        }
                        return TRUE;
                    }
                    default:
                        return CallNextHookEx(NULL, code, wParam, lParam);
                }
                break;
            case WM_KEYDOWN:
                switch (p->vkCode) {
                    case 0x60:
                    case 0x30:
                    case 0x61:
                    case 0x31:
                    case 0x62:
                    case 0x32:
                    case 0x63:
                    case 0x33:
                    case 0x64:
                    case 0x34:
                    case 0x65:
                    case 0x35:
                    case 0x66:
                    case 0x36:
                    case 0x67:
                    case 0x37:
                    case 0x68:
                    case 0x38:
                    case 0x69:
                    case 0x39:
                    case VK_BACK:
                    case VK_RETURN:
                        return TRUE;
                }
                break;
            default:
                return CallNextHookEx(NULL, code, wParam, lParam);
        }
    }
    else if (code > 0) {
        return CallNextHookEx(NULL, code, wParam, lParam);
    }
}
0
134 / 26 / 8
Регистрация: 09.02.2017
Сообщений: 175
10.01.2019, 02:52
Вы исправили свои проблемы или еще что-то осталось?
0
 Аватар для blbulyan
12 / 10 / 3
Регистрация: 31.10.2017
Сообщений: 126
15.01.2019, 19:54  [ТС]
Всё что было я исправил. Спасибо за ваш интерес к этой теме.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
15.01.2019, 19:54
Помогаю со студенческими работами здесь

Реализовать функцию на win32api
void CreateM() // Опис функції що створює повідомлення ресурсу пам'яті { BOOL bResLow; BOOL bResHigh; HANDLE hObjectLow =...

Нужна помощь Win32API
Помогите,плиз. Нужно сделать так, чтобы символ вводимый с клавиатуры , отображался в созданном окне. Окно создал а вот с событием WM_CHAR...

Приложение Win32api не работает на других пк
Есть приложение созданное с применением только Win32api . Не могу понять почему не работает на других пк? Выдает такую ошибку: ...

Реализация Docking Window Win32Api
Помогите разобраться в реализации Не знаю даже с чего начать. В интернете информации на данную тему очень мало ((((

Копирование текста в переменную win32api
Здравствуйте у меня есть диалог который должен считать количество переменных) Загружайте картинки на форум. помогите как и с...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
4
Ответ Создать тему
Новые блоги и статьи
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
Расчёт токов в цепи постоянного тока
igorrr37 05.01.2026
/ * Дана цепь постоянного тока с сопротивлениями и напряжениями. Надо найти токи в ветвях. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа и решает её. Последовательность действий:. . .
Новый CodeBlocs. Версия 25.03
palva 04.01.2026
Оказывается, недавно вышла новая версия CodeBlocks за номером 25. 03. Когда-то давно я возился с только что вышедшей тогда версией 20. 03. С тех пор я давно снёс всё с компьютера и забыл. Теперь. . .
Модель микоризы: классовый агентный подход
anaschu 02.01.2026
Раньше это было два гриба и бактерия. Теперь три гриба, растение. И на уровне агентов добавится между грибами или бактериями взаимодействий. До того я пробовал подход через многомерные массивы,. . .
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru