Обновить
125.11

Windows *

Разработка под операционные системы от Microsoft

Сначала показывать
Порог рейтинга

Навеяно сегодняшней статьёй «История: как Microsoft шесть раз отказывалась от виджетов, но потом возвращала их».

Понятно, что Майкрософт просто хочет содрать с бедолаги, купившего (тем более, скачавшего) винды, 33 шкуры. И поэтому делает как бы виджеты, но они все должны вести туда, куда хочет Майкрософт. В Copilot, MSN и тому подобные места. Но давайте помечтаем, что могла бы сделать Майкрософт, если бы по-настоящему хотела сделать своим юзерам удобно.

  1. Виджет это приложение, точка Приложения бывают хорошие, годные и злые, вредные. Любое приложение может сломать ваш компьютер, украсть данные и деньги. Ответственность делится так: автор приложения прилагает все усилия, чтобы приложение не делало ничего плохого. Юзер прилагает все усилия, чтобы не ставить подозрительные приложения хрен пойми откуда. Если вам кажется, что это наивно, вспомните, что именно эта схема действует на PC прямо сейчас. На Github'е @dartraiden выложил драйвер (даже не простое приложение!), который позволяет использовать недорогие карты для майнинга вместо видеокарт (за 10-15% от цены последних) и написал: «Если вы мне не доверяете, вот инструкция, как собрать драйвер самому». Спасибо, но что-то не хочется )). Собирать драйвер самому. Я доверяю автору проекта! А ещё — авторам редактора Notepad++, браузера Firefox, файлового менеджера FAR (который почти в каждой сессии просит права админа, потому что я захожу им в системные папки) и многим другим авторам приложений.

  2. Любое приложение может зарегистрировать себя в качестве виджета. Такая технология у Майкрософт уже есть, и называется ActiveX. Она до сих прекрасно работает (в день, когда сломается ActiveX, я перейду на Линукс, потому что только старые приложения меня под виндой и держат).

    Суть технологии в том, что у вас есть файл .dll, который регистрируется стандартной командой regsvr32 в той папке, где лежит (при переносе в другую папку его надо перерегистрировать). Чтобы не заставлять пользователя вручную выполнять эту команду, её обычно выполняет инсталлятор. (Или просто сам вызывает регистрирующую функцию из этой .dll — ходил анекдот про авторов инсталлятора, которые «сделали ядро в новой версии на X килобайт меньше, включив в него облегчённую версию regsvr32», потому что не знали азов программирования под Windows).

    Этот файл просто создаёт маленькое окошко (говоря техническим языком, window handle), может быть написан на C или Rust, занимать килобайты и работать со скоростью света.

    Всё, что вам нужно — дополнительно записывать при регистрации идентификатор своего ActiveX-компонента в ветку реестра с виджетами.

    Чтобы было проще создавать виджеты на HTML/CSS/JS, Майкрософт мог бы добавить новый тип проекта в Visual Studio: HTML Widget. Он брал бы файлы .html/.css/.js, метаданные и запаковывал бы в ActiveX-компонент вместе с WebView2 (браузерным окном). И ваш виджет отображался бы при помощи Chromium, как это делает приложение (не виджет) Steam, но весил бы, в отличие от Steam, ровно столько, сколько весят картинки и текст.

    Разумеется, ничто не мешало бы создать виджет на C#, Qt, Delphi, на базе своей версии Chromium (как это делает Steam), на базе Gecko, на базе чего угодно.

  3. Будучи хозяйкой бала, Windows предлагала бы юзеру зарегистрированные виджеты перетаскивать из палитры виджетов в следующие места: а) рабочий стол, б) панель задач, в) меню «Пуск». Разумеется, любой виджет обязан был бы имплементировать помимо стандартных интерфейсов ActiveX-компонента специальный интерфейс IAmWidget и через него рассказывать о своих требованиях. Так что виджет, которому нужна минимальная площадь 512x512 пикселей, можно было бы создать только на рабочем столе.

  4. В каждом из этих мест виджет вписывался бы в стандартную сетку (на рабочем столе такая используется для выравнивания ярлыков, на панели задач — это квадратик ярлыка или прямоугольник приложения). Поместить другие элементы (ярлыки) в занятое место было бы нельзя.

Теги:
0
Комментарии0

MiniFilter и Protector/Rejector (ObCallback) в одном драйвере с управлением через C#

В продолжение этого поста.

Предлагаю вашему внимаю мою поделку основанную на MiniFilter, ObCallback и Avalonia

Можно грабить корованы защищать от закрытия, регулировать доступ к файлам и запрещать запускать процессы.

C# код для управления драйвером:

using System;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Threading;
using SharpMiniFilter.Driver.MiniFilter;
using SharpMiniFilter.Driver.Protector;

namespace SharpMiniFilter.Protected;

public partial class MainWindow : Window
{
    private bool allowClose = false;
    
    public MainWindow()
    {
        InitializeComponent();
        this.Closing += (sender, args) =>
        {
            args.Cancel = !allowClose;

            if (!args.Cancel)
            {
                ProtectorClient.ReplaceProtectList(Array.Empty<string>());
                ProtectorClient.ReplaceRejectList(Array.Empty<string>());
                MiniFilterClient.CloseConnection();
                MiniFilterClient.DriverFilter -= DriverClientOnDriverFilter;
            }
        };
        
        MiniFilterClient.DriverFilter += DriverClientOnDriverFilter;
        
        if (MiniFilterClient.Connect())
        {
            ProtectorClient.ReplaceProtectList(new[] { $"PID:{Process.GetCurrentProcess().Id}" });
            ProtectorClient.ReplaceRejectList(new[] {  "*cmd.exe" });
            
            Log_TextBox.Text += "Added current process to protection list." + Environment.NewLine;
            Log_TextBox.Text += "Added cmd.exe to reject list." + Environment.NewLine;
        }
        else
        {
            Log_TextBox.Text += "Connection to driver failed." + Environment.NewLine;
            MiniFilterClient.DriverFilter -= DriverClientOnDriverFilter;
        }
    }

    private void DriverClientOnDriverFilter(MinifilterEventArgs e)
    {
        Dispatcher.UIThread.Invoke(() =>
        {
            if (e.Path.Contains("test.txt"))
            {
                if (!Process.GetProcessById((int)e.ProcessId).ProcessName.ToLower().Contains("notepad"))
                {
                    e.SetHandled(true);
                    Log_TextBox.Text += "Minifilter: test.txt blocked" + Environment.NewLine;
                }
                else
                {
                    e.SetHandled(false);
                    Log_TextBox.Text += "Minifilter: test.txt not blocked for notepad.exe" + Environment.NewLine;
                }
            }
        });
    }

    private void Button_OnClick(object? sender, RoutedEventArgs e)
    {
        allowClose = true;
        this.Close();
    }
}

Бонусом - создание .cab файла для отправки в Microsoft на сертификацию при Release сборке.

Ссылка на репозиторий.

P.S. Если вам будет интересно, а у меня силы и карма - то расскажу, что там и как в отдельной статье. А теперь и ответ на всех мучающий вопрос: "Почему пингвин пошёл в горы?"

Теги:
0
Комментарии2

Отправка уведомления от имени другого приложения

Результат
Результат

В продолжение этого поста

Когда у вас AOT приложение, которое запускается от администратора - 99% библиотек для работы с COM не работают, плюс для Windows App SDK отдельно указано "Notifications for an elevated (admin) app is currently not supported.", а issue на github висит с 2023 года и поэтому для отправки системных уведомлений надо выкручиваться через другое приложение/PowerShell.

Пример как отправлять уведомления:

using System.Diagnostics;
using Windows.UI.Notifications;
using Microsoft.Toolkit.Uwp.Notifications;
using Vanara.PInvoke;
using Vanara.Windows.Shell;

namespace ConsoleNotifications;

class Program
{
    static async Task Main(string[] args)
    {
        string messageTitle = "Habr";
        string messageText = "Hello Habrahabr!";
        string targetExePath = Environment.SystemDirectory + "\\notepad.exe";
        string appUserModelId = GetAppAumid().First(var=>var.Name.Contains("Chrome")).Aumid;
        await SendNotification(appUserModelId, messageTitle, messageText, targetExePath);
    }

    private static Task SendNotification(string appUserModelId, string title, string content, string targetPath)
    {
        var tcs = new TaskCompletionSource();
        var notification = new ToastNotification(new ToastContentBuilder().AddText(title)
            .AddText(content).Content.GetXml());
        notification.Priority = ToastNotificationPriority.High;
        notification.Activated += (s, e) =>
        {
            Process.Start(new ProcessStartInfo(targetPath));
            tcs.SetResult();
        };
        notification.Failed += (s, e) => tcs.SetResult();
        notification.Dismissed += (s, e) => tcs.SetResult();
        ToastNotificationManager.CreateToastNotifier(appUserModelId).Show(notification);
        return tcs.Task;
    }
    
    public sealed record AppAumidInfo(string Name, string Aumid);
    
    public static List<AppAumidInfo> GetAppAumids()
    {
        var results = new List<AppAumidInfo>();
        var f = new ShellFolder(Shell32.KNOWNFOLDERID.FOLDERID_AppsFolder);
        foreach (ShellItem i in f.EnumerateChildren(FolderItemFilter.NonFolders | FolderItemFilter.Folders))
            results.Add(new AppAumidInfo(i.Name, i.ParsingName));
        return results;
    }
}

В вызываемом приложении надо выставить AppUserModelID и предварительно создать ссылку на него

[LibraryImport("shell32.dll", SetLastError=true)]
internal static partial void SetCurrentProcessExplicitAppUserModelID([MarshalAs(UnmanagedType.LPWStr)] string AppID);

Создание .lnk ссылки с параметрами:

var link = new ShellLink()
{
        ToastActivatorId = guid, 
        AppUserModelID = appUserModelId,
        TargetPath = exePath
};
link.Save(shortcutPath);

P.S. Буду рад услышать про другие существующие решения этой проблемы

Теги:
+3
Комментарии0

Открытый проект CompactGUI оптимизирует дисковое пространство, занятое играми. Степень сжатия — до 60%. Это не архивация — игры не запаковываются в ZIP и RAR, тут совершенно другой метод. Игры продолжат работать как обычно — ярлыки и папки будут на месте. Если игра обновилась в Steam, то решение само досожмёт обновление. Проект использует встроенные алгоритмы Windows.

Теги:
0
Комментарии0

На российских маркетплейсах начали продавать игры на флешках — селлеры просто качают тайтлы с торрентов, подгружают на флешки и продают по 1300 рублей за штуку.

Теги:
+1
Комментарии4

Чего только не найти в материалах дела Джеффри Эпштейна! Вообще, под файлами Эпштейна обычно понимают сразу несколько разных сборников документов: что-то выкладывали ФБР и Минюст США на подсайте justice.gov/epstein, другие публичились через суды (когда по отдельным процессам судьи разрешали снять ограничения на приложения, показания и переписку), а третья часть разошлась по Интернету в виде репостов журналистских находок, выдач по закону Freedom of Information Act и утечек, где первоисточник уже не всегда очевиден.

Больше всего ярких эмоций вызывает вопрос, кто из власть имущих пользовался услугами «Лолита-экспресса» — принадлежавшего Эпштейну Boeing 727-100. Вернее, хочется в первую очередь узнать, почему нам до сих пор не раскрыли полный список летавших на частный остров этого финансиста с непонятной биографией.

Поэтому как только любые материалы дела попадают в общий доступ, обыватели немедленно начинают в них рыться. А там не только сканы документов, там есть и просто фотографии Эпштейна, его партнёра по тёмным делишкам Гислейн Максвелл и каких-то людей с зацензуренными лицами. Эти личные фотоальбомы (часто очень откровенные) с улюлюканьем репостят по сайтам социальных сетей. Как шутит автор канала Good Work Дэн Туми, дело Эпштейна навсегда испортило общественное восприятие винтажных фотокамер: теперь впору на каждой выцветшей зернистой пляжной фотографии рисовать пометку «нет, это снято не на Виргинских островах».

Наконец, тщательно копаются даже в фотографиях имущества. 18-летний микроблогер possiblyazure поделился следующей находкой: файл EFTA00002467.pdf состоит из одной фотографии, где запечатлён перевёрнутый ноутбук Toshiba. В том числе заметен и хорошо читается продуктовый ключ для Windows 7 Home Premium.

Как рассказывает подросток, ключ до сих пор работает. Вероятно, из-за роста числа использований система пометила его как утекший, поэтому придётся полагаться на активацию по телефону. Под последним ныне понимается посещение специальной веб-страницы, где нужно ввести идентификатор установки, получить специальный код и ввести эти 48 цифр в форму активации.

Непонятно, можно ли переиспользовать подобный ключ для Windows 10 и 11, да и законность действий под большим вопросом.

Теги:
+6
Комментарии1

Устранена проблему, из-за которой установщики Adobe Creative Cloud для Windows не могли работать в Linux через Wine из-за некоторых несовместимостей Wine с MSXML3 и MSHTML. После этого открытого фикса Adobe Photoshop 2021 и Photoshop 2025 могут быть установлены и запущены в Linux через Wine.

Теги:
+7
Комментарии1

Прослушивание музыки на коммуникаторах - это что-то с чем-то!⁠

Такой ламповый и удобный Windows Media Player, очень достойное качество звука для среднебюджетного коммуникатора 2005 года и приятный форм-фактор. HTC Wizard с слабеньким OMAP'ом удивляет даже сейчас! Жаль только треки очень долго закидывать на карту через WMDC. Благо карты памяти быстросъемные и можно просто подкинуть в кардридер :)

Теги:
+5
Комментарии2

Энтузиаст показал давно забытый секрет популярного офисного пакета Microsoft Office 97 — как открыть скрытые титры. Для этого нужно передвинуть окно программы в определённые места на экране, а затем ввести специальную фразу для Скрепыша: «This is not a contest». После этого появляется новое окно с яркой анимированной заставкой и титрами, которые длятся около трёх минут. Во время показа титров Скрепыш «рассказывает» о людях, которые создавали программу, добавляя шутливые реплики.

Теги:
+3
Комментарии0

Представлен проект CapacityTester — утилита с графическим интерфейсом для выявления реальной ёмкости носителей информации. Решение кроссплатформенное, написано на C++ и создано с использованием фреймворка Qt.

Есть два режима работы CapacityTester:

  1. Аналогичный используемому при работе консольных утилит f3write/f3read (пакет f3 — Fight Flash Fraud), когда свободное место на носителе (с файловой системой) заполняется специально сформированными файлами. На носителях большого объёма требуется длительное время для проверки.

  2. Деструктивный режим, когда данные пишутся напрямую на носитель, и фейковая ёмкость может быть выявлена быстрее (у f3 тоже, вроде бы, есть аналогичный режим, но это не точно).

Помимо авторских сборок, у программы есть пакет в репозиториях Altlinux и PKGBUILD в AUR.

Теги:
+3
Комментарии0

Открытый проект SpotX позволяет бесплатно слушать Spotify без рекламы и со всеми Premium-опциями. Всё, что нужно — актуальная версия Spotify и запустить на ПК одну команду.

Для Windows пишем в Powershell: iex "& { $(iwr -useb 'https://raw.githubusercontent.com/SpotX-Official/SpotX/refs/heads/main/run.ps1') } -new_theme".

Для MacOS, пишем в Терминале: bash <(curl -sSL https://spotx-official.github.io/run.sh).

Теги:
+2
Комментарии1

Microsoft не смогла сдержать свои обещания - компания добавила ИИ-поиск в настройки Windows 11, но он не работает с фразой, которую разработчики предлагают для теста.

Теги:
Всего голосов 3: ↑3 и ↓0+3
Комментарии0

Обновлён сборник твикеров, кастомайзеров и проверенных системных решений для Windows под названием System Tools. База данных проект включает ссылки на популярные и полезные утилиты для очистки, твика, удалённого доступа, мониторинга, виртуальных машин, менеджеров дисков, периферии и аудио в системе.

Теги:
Всего голосов 1: ↑1 и ↓0+2
Комментарии0

Ближайшие события

Открытый проект Digler помогает спасти удалённые файлы на жёстком диске, проводит глубокий анализ SSD или HDD и может вернут утерянные данные. Работает со всеми файловыми системами, даже если метаданные отсутствуют. Сканирует не только физические SSD, но и образы дисков. Создаёт детальные отчёты, которые помогут точечно спасти нужные файлы. Умеет работать с файлами любых форматов.

Теги:
Всего голосов 2: ↑1 и ↓10
Комментарии1

94 ГБ ОЗУ может утилизировать приложение Discord — реддитор поделился скриншотом этой ситуации. Он надеется, что фича с принудительной перезагрузкой приложения исправит ситуацию.

Ранее разработчики Discord для Windows «научили» мессенджер автоматически перезапускаться в фоновом режиме, если он использует более 4 ГБ ОЗУ. На Reddit создатели объяснили, что это часть текущего исследования и временная мера, призванная снизить нагрузку на память, с которой сталкиваются пользователи.

Теги:
Всего голосов 5: ↑4 и ↓1+5
Комментарии0

Стоит ли использовать компрессию NTFS одновременно с дедупликацией Windows?

TLDR; Нет.

Сжатие NTFS (NTFS compression) было эффективным средством экономии места на файловых серверах до появления дедупликации Windows.

Эффективность совместного использования компрессии и дедупликации всегда была под вопросом, так как дедупликация по умолчанию сжимает данные чанков. Сжатие можно опционально отключить для определенных типов файлов (-NoCompressionFileType), или отключить полностью (-NoCompress $true).

Осторожно! Если дедуплицировать том, на котором NTFS компрессия включена на уровне тома (а не отдельных папок), это приведет к необратимому повреждению файлов.

Ответ на вопрос поста получим опытным путём.
Эксперимент проведен на папке с пользовательскими данными размером 1,58 TB, 2 063 394 Files, 257 482 Folders.

Результат дедупликации тома с несжатой папкой:
PS C:\Windows\system32> Get-DedupStatus M:
FreeSpace SavedSpace OptimizedFiles InPolicyFiles Volume
584.8 GB 1.76 TB 2162406 2162390 M:

Сжимаем папку NTFS compression (compact /c /s /i) и повторяем эксперимент:
PS C:\Windows\system32> Get-DedupStatus M:
FreeSpace SavedSpace OptimizedFiles InPolicyFiles Volume
343.11 GB 1.44 TB 1578839 2157096 M:

После дедупликации тома со сжатой папкой свободно осталось лишь 343 Gb (против 584 Gb), данные занимают на 205 Gb больше.

Вывод - одновременное использование NTFS compression и Windows Deduplication с высокой вероятностью будет менее эффективно, чем использование только Windows Deduplication.

Теги:
Всего голосов 1: ↑1 и ↓0+2
Комментарии2

Проект Remove Windows Ai позволяет с помощью одного открытого скрипа удалить ИИ-мусор из Windows 11 за два клика: Copilot, Recall, ИИ в Пейнте, браузере, поиске Windows. В Powershell под администратором (если вы уверены на свой страх и риск, что это правильно и нужно вам): () & ([scriptblock]::Create((irm "https://raw.githubusercontent.com/zoicware/RemoveWindowsAI/main/RemoveWindowsAi.ps1"))).

Теги:
Всего голосов 6: ↑4 и ↓2+2
Комментарии0

Определение типа fullscreen

Понадобилось мне как‑то определять тип fullscreen для вывода подходящего типа уведомления (звуковое/системное/окном) во время запущенных игр и родился такой код:

    public enum WindowFullscreenState
    {
        None,
        Emulated,
        Shared,
        Exclusive,
        ExclusiveGdi,
        NotOwned
    }

    public static WindowFullscreenState GetForegroundWindowFullscreenType()
    {
        var result = WindowFullscreenState.None;   
        uint dwProcessId;
        D3DKMT_QUERYVIDPNEXCLUSIVEOWNERSHIP query = new D3DKMT_QUERYVIDPNEXCLUSIVEOWNERSHIP
        {
            hWindow = GetForegroundWindow()
        };

        GetWindowThreadProcessId(query.hWindow, out dwProcessId);

        query.hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, dwProcessId);
        if (query.hProcess != IntPtr.Zero)
        {
            D3DKMTQueryVidPnExclusiveOwnership(ref query);
            CloseHandle(query.hProcess);

            result = query.OwnerType switch
            {
                D3DKMT_VIDPNSOURCEOWNER_TYPE.D3DKMT_VIDPNSOURCEOWNER_UNOWNED => WindowFullscreenState.NotOwned,
                D3DKMT_VIDPNSOURCEOWNER_TYPE.D3DKMT_VIDPNSOURCEOWNER_SHARED => WindowFullscreenState.Shared,
                D3DKMT_VIDPNSOURCEOWNER_TYPE.D3DKMT_VIDPNSOURCEOWNER_EXCLUSIVE => WindowFullscreenState.Exclusive,
                D3DKMT_VIDPNSOURCEOWNER_TYPE.D3DKMT_VIDPNSOURCEOWNER_EXCLUSIVEGDI => WindowFullscreenState.ExclusiveGdi,
                D3DKMT_VIDPNSOURCEOWNER_TYPE.D3DKMT_VIDPNSOURCEOWNER_EMULATED => WindowFullscreenState.Emulated,
                _ =>WindowFullscreenState.None
            };
        }

        return result;
    }

    public enum D3DKMT_VIDPNSOURCEOWNER_TYPE
    {
        D3DKMT_VIDPNSOURCEOWNER_UNOWNED = 0,
        D3DKMT_VIDPNSOURCEOWNER_SHARED = 1,
        D3DKMT_VIDPNSOURCEOWNER_EXCLUSIVE = 2,
        D3DKMT_VIDPNSOURCEOWNER_EXCLUSIVEGDI = 3,
        D3DKMT_VIDPNSOURCEOWNER_EMULATED = 4
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public uint LowPart;
        public int HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct D3DKMT_QUERYVIDPNEXCLUSIVEOWNERSHIP
    {
        public IntPtr hProcess;
        public IntPtr hWindow;
        public uint VidPnSourceId;
        public LUID AdapterLuid;
        public D3DKMT_VIDPNSOURCEOWNER_TYPE OwnerType;
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hObject);

    [DllImport("user32.dll", CharSet = CharSet.Unicode)]
    private static extern int MessageBoxW(IntPtr hWnd, string lpText, string lpCaption, uint uType);

    private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;

    [DllImport("gdi32.dll", EntryPoint = "D3DKMTQueryVidPnExclusiveOwnership")]
    private static extern int D3DKMTQueryVidPnExclusiveOwnership(ref D3DKMT_QUERYVIDPNEXCLUSIVEOWNERSHIP data);
    
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();

Схема использования с типами уведомлений:

WindowFullscreenState.Exclusive -> SendVoiceNotification
WindowFullscreenState.ExclusiveGdi -> SendVoiceNotification
WindowFullscreenState.Emulated -> ShowSystemNotification
WindowFullscreenState.Shared -> ShowSystemNotification
WindowFullscreenState.NotOwned || WindowFullscreenState.None -> IsForegroundWindowSmallerThanScreen ? ShowCustomWindow : ShowSystemNotification

Подробнее можно прочитать про виды захвата экрана тут и тут.

P.S. Некоторые игры (ExclusiveGdi) можно заставить (Emulated) получать системные уведомления через режим совместимости.

Теги:
Всего голосов 5: ↑4 и ↓1+3
Комментарии0

Блогер показа самый иммерсивный рабочий стол по Minecraft. Ярлыки программ и игр красивенько размещены в слотах персонажа — можно даже открывать «инвентарь» персонажа. Для такого модного сетапа понадобятся три вещи — Windows 11, Rainmeter Skins и Wallpaper Engine. Последние две программы позволяют создать обои на любую тематику.

Теги:
Всего голосов 2: ↑0 и ↓2-2
Комментарии2

Терминал, командная строка, консоль, shell — как правильно?

Чем эти все термины отличаются друг для друга. Очень нужно понять, чтобы правильно написать мою следующую книгу.

Всем ответившим заранее спасибо.

Теги:
Всего голосов 6: ↑1 и ↓5-3
Комментарии12