Compare commits

..

No commits in common. "dev" and "v4.8.11" have entirely different histories.
dev ... v4.8.11

2624 changed files with 70443 additions and 280637 deletions

View file

@ -1,57 +0,0 @@
---
Language: Cpp
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignOperands: false
AlignTrailingComments: false
AllowShortCaseLabelsOnASingleLine: true
AllowShortIfStatementsOnASingleLine: true
AllowShortLoopsOnASingleLine: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: false
AfterNamespace: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: true
BeforeWhile: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBraces: Custom
ColumnLimit: 120
IncludeCategories:
- Regex: '^<.*'
Priority: 1
- Regex: '^".*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentWidth: 4
InsertNewlineAtEOF: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 2
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceInEmptyParentheses: false
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
TabWidth: 4
UseTab: Always
...

View file

@ -1,100 +0,0 @@
# Telegram Desktop API Usage
## API Schema
The API definitions are described using [TL Language](https://core.telegram.org/mtproto/TL) in two main schema files:
1. **`Telegram/SourceFiles/mtproto/scheme/mtproto.tl`**
* Defines the core MTProto protocol types and methods used for basic communication, encryption, authorization, service messages, etc.
* Some fundamental types and methods from this schema (like basic types, RPC calls, containers) are often implemented directly in the C++ MTProto core (`SourceFiles/mtproto/`) and may be skipped during the C++ code generation phase.
* Other parts of `mtproto.tl` might still be processed by the code generator.
2. **`Telegram/SourceFiles/mtproto/scheme/api.tl`**
* Defines the higher-level Telegram API layer, including all the methods and types related to chat functionality, user profiles, messages, channels, stickers, etc.
* This is the primary schema used when making functional API requests within the application.
Both files use the same TL syntax to describe API methods (functions) and types (constructors).
## Code Generation
A custom code generation tool processes `api.tl` (and parts of `mtproto.tl`) to create corresponding C++ classes and types. These generated headers are typically included via the Precompiled Header (PCH) for the main `Telegram` project.
Generated types often follow the pattern `MTP[Type]` (e.g., `MTPUser`, `MTPMessage`) and methods correspond to functions within the `MTP` namespace or related classes (e.g., `MTPmessages_SendMessage`).
## Making API Requests
API requests are made using a standard pattern involving the `api()` object (providing access to the `MTP::Instance`), the generated `MTP...` request object, callback handlers for success (`.done()`) and failure (`.fail()`), and the `.send()` method.
Here's the general structure:
```cpp
// Include necessary headers if not already in PCH
// Obtain the API instance (usually via api() or MTP::Instance::Get())
api().request(MTPnamespace_MethodName(
// Constructor arguments based on the api.tl definition for the method
MTP_flags(flags_value), // Use MTP_flags if the method has flags
MTP_inputPeer(peer), // Use MTP_... types for parameters
MTP_string(messageText),
MTP_long(randomId),
// ... other arguments matching the TL definition
MTP_vector<MTPMessageEntity>() // Example for a vector argument
)).done([=](const MTPResponseType &result) {
// Handle the successful response (result).
// 'result' will be of the C++ type corresponding to the TL type
// specified after the '=' in the api.tl method definition.
// How to access data depends on whether the TL type has one or multiple constructors:
// 1. Multiple Constructors (e.g., User = User | UserEmpty):
// Use .match() with lambdas for each constructor:
result.match([&](const MTPDuser &data) {
/* use data.vfirst_name().v, etc. */
}, [&](const MTPDuserEmpty &data) {
/* handle empty user */
});
// Alternatively, check the type explicitly and use the constructor getter:
if (result.type() == mtpc_user) {
const auto &data = result.c_user(); // Asserts if type is not mtpc_user!
// use data.vfirst_name().v
} else if (result.type() == mtpc_userEmpty) {
const auto &data = result.c_userEmpty();
// handle empty user
}
// 2. Single Constructor (e.g., Messages = messages { msgs: vector<Message> }):
// Use .match() with a single lambda:
result.match([&](const MTPDmessages &data) { /* use data.vmessages().v */ });
// Or check the type explicitly and use the constructor getter:
if (result.type() == mtpc_messages) {
const auto &data = result.c_messages(); // Asserts if type is not mtpc_messages!
// use data.vmessages().v
}
// Or use the shortcut .data() for single-constructor types:
const auto &data = result.data(); // Only works for single-constructor types!
// use data.vmessages().v
}).fail([=](const MTP::Error &error) {
// Handle the API error (error).
// 'error' is an MTP::Error object containing the error code (error.type())
// and description (error.description()). Check for specific error strings.
if (error.type() == u"FLOOD_WAIT_X"_q) {
// Handle flood wait
} else {
Ui::show(Box<InformBox>(Lang::Hard::ServerError())); // Example generic error handling
}
}).handleFloodErrors().send(); // handleFloodErrors() is common, then send()
```
**Key Points:**
* Always refer to `Telegram/SourceFiles/mtproto/scheme/api.tl` for the correct method names, parameters (names and types), and response types.
* Use the generated `MTP...` types/classes for request parameters (e.g., `MTP_int`, `MTP_string`, `MTP_bool`, `MTP_vector`, `MTPInputUser`, etc.) and response handling.
* The `.done()` lambda receives the specific C++ `MTP...` type corresponding to the TL return type.
* For types with **multiple constructors** (e.g., `User = User | UserEmpty`), use `result.match([&](const MTPDuser &d){ ... }, [&](const MTPDuserEmpty &d){ ... })` to handle each case, or check `result.type() == mtpc_user` / `mtpc_userEmpty` and call the specific `result.c_user()` / `result.c_userEmpty()` getter (which asserts on type mismatch).
* For types with a **single constructor** (e.g., `Messages = messages{...}`), you can use `result.match([&](const MTPDmessages &d){ ... })` with one lambda, or check `type()` and call `c_messages()`, or use the shortcut `result.data()` to access the fields directly.
* The `.fail()` lambda receives an `MTP::Error` object. Check `error.type()` against known error strings (often defined as constants or using `u"..."_q` literals).
* Directly construct the `MTPnamespace_MethodName(...)` object inside `request()`.
* Include `.handleFloodErrors()` before `.send()` for standard flood wait handling.

View file

@ -1,159 +0,0 @@
# Telegram Desktop Localization
## Coding Style Note
**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice.
```cpp
// Prefer this:
auto currentTitle = tr::lng_settings_title(tr::now);
auto nameProducer = GetNameProducer(); // Returns rpl::producer<...>
// Instead of this:
QString currentTitle = tr::lng_settings_title(tr::now);
rpl::producer<QString> nameProducer = GetNameProducer();
```
## String Resource File
Base user-facing English strings are defined in the `lang.strings` file:
`Telegram/Resources/langs/lang.strings`
This file uses a key-value format with named placeholders:
```
"lng_settings_title" = "Settings";
"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?";
"lng_files_selected" = "{count} files selected"; // Simple count example (see Pluralization)
```
Placeholders are enclosed in curly braces, e.g., `{name}`, `{user}`. A special placeholder `{count}` is used for pluralization rules.
### Pluralization
For keys that depend on a number (using the `{count}` placeholder), English typically requires two forms: singular and plural. These are defined in `lang.strings` using `#one` and `#other` suffixes:
```
"lng_files_selected#one" = "{count} file selected";
"lng_files_selected#other" = "{count} files selected";
```
While only `#one` and `#other` are defined in the base `lang.strings`, the code generation process creates C++ accessors for all six CLDR plural categories (`#zero`, `#one`, `#two`, `#few`, `#many`, `#other`) to support languages with more complex pluralization rules.
## Translation Process
While `lang.strings` provides the base English text and the keys, the actual translations are managed via Telegram's translations platform (translations.telegram.org) and loaded dynamically at runtime from the API. The keys from `lang.strings` (including the `#one`/`#other` variants) are used on the platform.
## Code Generation
A code generation tool processes `lang.strings` to create C++ structures and accessors within the `tr` namespace. These allow type-safe access to strings and handling of placeholders and pluralization. Generated keys typically follow the pattern `tr::lng_key_name`.
## String Usage in Code
Strings are accessed in C++ code using the generated objects within the `tr::` namespace. There are two main ways to use them: reactively (returning an `rpl::producer`) or immediately (returning the current value).
### 1. Reactive Usage (rpl::producer)
Calling a generated string function directly returns a reactive producer, typically `rpl::producer<QString>`. This producer automatically updates its value whenever the application language changes.
```cpp
// Key: "settings_title" = "Settings";
auto titleProducer = tr::lng_settings_title(); // Type: rpl::producer<QString>
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
auto itemNameProducer = /* ... */; // Type: rpl::producer<QString>
auto confirmationProducer = tr::lng_confirm_delete_item( // Type: rpl::producer<QString>
tr::now, // NOTE: tr::now is NOT passed here for reactive result
lt_item_name,
std::move(itemNameProducer)); // Placeholder producers should be moved
```
### 2. Immediate Usage (Current Value)
Passing `tr::now` as the first argument retrieves the string's current value in the active language (typically as a `QString`).
```cpp
// Key: "settings_title" = "Settings";
auto currentTitle = tr::lng_settings_title(tr::now); // Type: QString
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
const auto currentItemName = QString("My Document"); // Type: QString
auto currentConfirmation = tr::lng_confirm_delete_item( // Type: QString
tr::now, // Pass tr::now for immediate value
lt_item_name, currentItemName); // Placeholder value is a direct QString (or convertible)
```
### 3. Placeholders (`{tag}`)
Placeholders like `{item_name}` are replaced by providing arguments after `tr::now` (for immediate) or as the initial arguments (for reactive). A corresponding `lt_tag_name` constant is passed before the value.
* **Immediate:** Pass the direct value (e.g., `QString`, `int`).
* **Reactive:** Pass an `rpl::producer` of the corresponding type (e.g., `rpl::producer<QString>`). Remember to `std::move` the producer or use `rpl::duplicate` if you need to reuse the original producer afterwards.
### 4. Pluralization (`{count}`)
Keys using `{count}` require a numeric value for the `lt_count` placeholder. The correct plural form (`#zero`, `#one`, ..., `#other`) is automatically selected based on this value and the current language rules.
* **Immediate (`tr::now`):** Pass a `float64` or `int` (which is auto-converted to `float64`).
```cpp
int count = 1;
auto filesText = tr::lng_files_selected(tr::now, lt_count, count); // Type: QString
count = 5;
filesText = tr::lng_files_selected(tr::now, lt_count, count); // Uses "files_selected#other"
```
* **Reactive:** Pass an `rpl::producer<float64>`. Use the `tr::to_count()` helper to convert an `rpl::producer<int>` or wrap a single value.
```cpp
// From an existing int producer:
auto countProducer = /* ... */; // Type: rpl::producer<int>
auto filesTextProducer = tr::lng_files_selected( // Type: rpl::producer<QString>
lt_count,
countProducer | tr::to_count()); // Use tr::to_count() for conversion
// From a single int value wrapped reactively:
int currentCount = 5;
auto filesTextProducerSingle = tr::lng_files_selected( // Type: rpl::producer<QString>
lt_count,
rpl::single(currentCount) | tr::to_count());
// Alternative for single values (less common): rpl::single(currentCount * 1.)
```
### 5. Custom Projectors
An optional final argument can be a projector function (like `Ui::Text::Upper` or `Ui::Text::WithEntities`) to transform the output.
* If the projector returns `OutputType`, the string function returns `OutputType` (immediate) or `rpl::producer<OutputType>` (reactive).
* Placeholder values must match the projector's *input* requirements. For `Ui::Text::WithEntities`, placeholders expect `TextWithEntities` (immediate) or `rpl::producer<TextWithEntities>` (reactive).
```cpp
// Immediate with Ui::Text::WithEntities projector
// Key: "user_posted_photo" = "{user} posted a photo";
const auto userName = TextWithEntities{ /* ... */ }; // Type: TextWithEntities
auto message = tr::lng_user_posted_photo( // Type: TextWithEntities
tr::now,
lt_user,
userName, // Must be TextWithEntities
Ui::Text::WithEntities); // Projector
// Reactive with Ui::Text::WithEntities projector
auto userNameProducer = /* ... */; // Type: rpl::producer<TextWithEntities>
auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer<TextWithEntities>
lt_user,
std::move(userNameProducer), // Move placeholder producers
Ui::Text::WithEntities); // Projector
```
## Key Summary
* Keys are defined in `Resources/langs/lang.strings` using `{tag}` placeholders.
* Plural keys use `{count}` and have `#one`/`#other` variants in `lang.strings`.
* Access keys via `tr::lng_key_name(...)` in C++.
* Call with `tr::now` as the first argument for the immediate `QString` (or projected type).
* Call without `tr::now` for the reactive `rpl::producer<QString>` (or projected type).
* Provide placeholder values (`lt_tag_name, value`) matching the usage (direct value for immediate, `rpl::producer` for reactive). Producers should typically be moved via `std::move`.
* For `{count}`:
* Immediate: Pass `int` or `float64`.
* Reactive: Pass `rpl::producer<float64>`, typically by converting an `int` producer using `| tr::to_count()`.
* Optional projector function as the last argument modifies the output type and required placeholder types.
* Actual translations are loaded at runtime from the API.

View file

@ -1,211 +0,0 @@
# RPL (Reactive Programming Library) Guide
## Coding Style Note
**Use `auto`:** In the actual codebase, variable types are almost always deduced using `auto` (or `const auto`, `const auto &`) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer `auto` in practice.
```cpp
// Prefer this:
auto intProducer = rpl::single(123);
const auto &lifetime = existingLifetime;
// Instead of this:
rpl::producer<int> intProducer = rpl::single(123);
const rpl::lifetime &lifetime = existingLifetime;
// Sometimes needed if deduction is ambiguous or needs help:
auto user = std::make_shared<UserData>();
auto data = QByteArray::fromHex("...");
```
## Introduction
RPL is the reactive programming library used in this project, residing in the `rpl::` namespace. It allows handling asynchronous streams of data over time.
The core concept is the `rpl::producer`, which represents a stream of values that can be generated over a certain lifetime.
## Producers: `rpl::producer<Type, Error = no_error>`
The fundamental building block is `rpl::producer<Type, Error>`. It produces values of `Type` and can optionally signal an error of type `Error`. By default, `Error` is `rpl::no_error`, indicating that the producer does not explicitly handle error signaling through this mechanism.
```cpp
// A producer that emits integers.
auto intProducer = /* ... */; // Type: rpl::producer<int>
// A producer that emits strings and can potentially emit a CustomError.
auto stringProducerWithError = /* ... */; // Type: rpl::producer<QString, CustomError>
```
Producers are typically lazy; they don't start emitting values until someone subscribes to them.
## Lifetime Management: `rpl::lifetime`
Reactive pipelines have a limited duration, managed by `rpl::lifetime`. An `rpl::lifetime` object essentially holds a collection of cleanup callbacks. When the lifetime ends (either explicitly destroyed or goes out of scope), these callbacks are executed, tearing down the associated pipeline and freeing resources.
```cpp
rpl::lifetime myLifetime;
// ... later ...
// myLifetime is destroyed, cleanup happens.
// Or, pass a lifetime instance to manage a pipeline's duration.
rpl::lifetime &parentLifetime = /* ... get lifetime from context ... */;
```
## Starting a Pipeline: `rpl::start_...`
To consume values from a producer, you start a pipeline using one of the `rpl::start_...` methods. These methods subscribe to the producer and execute callbacks for the events they handle.
The most common method is `rpl::start_with_next`:
```cpp
auto counter = /* ... */; // Type: rpl::producer<int>
rpl::lifetime lifetime;
// Counter is consumed here, use std::move if it's an l-value.
std::move(
counter
) | rpl::start_with_next([=](int nextValue) {
// Process the next integer value emitted by the producer.
qDebug() << "Received: " << nextValue;
}, lifetime); // Pass the lifetime to manage the subscription.
// Note: `counter` is now in a moved-from state and likely invalid.
// If you need to start the same producer multiple times, duplicate it:
// rpl::duplicate(counter) | rpl::start_with_next(...);
// If you DON'T pass a lifetime to a start_... method:
auto counter2 = /* ... */; // Type: rpl::producer<int>
rpl::lifetime subscriptionLifetime = std::move(
counter2
) | rpl::start_with_next([=](int nextValue) { /* ... */ });
// The returned lifetime MUST be stored. If it's discarded immediately,
// the subscription stops instantly.
// `counter2` is also moved-from here.
```
Other variants allow handling errors (`_error`) and completion (`_done`):
```cpp
auto dataStream = /* ... */; // Type: rpl::producer<QString, Error>
rpl::lifetime lifetime;
// Assuming dataStream might be used again, we duplicate it for the first start.
// If it's the only use, std::move(dataStream) would be preferred.
rpl::duplicate(
dataStream
) | rpl::start_with_error([=](Error &&error) {
// Handle the error signaled by the producer.
qDebug() << "Error: " << error.text();
}, lifetime);
// Using dataStream again, perhaps duplicated again or moved if last use.
rpl::duplicate(
dataStream
) | rpl::start_with_done([=]() {
// Execute when the producer signals it's finished emitting values.
qDebug() << "Stream finished.";
}, lifetime);
// Last use of dataStream, so we move it.
std::move(
dataStream
) | rpl::start_with_next_error_done(
[=](QString &&value) { /* handle next value */ },
[=](Error &&error) { /* handle error */ },
[=]() { /* handle done */ },
lifetime);
```
## Transforming Producers
RPL provides functions to create new producers by transforming existing ones:
* `rpl::map`: Transforms each value emitted by a producer.
```cpp
auto ints = /* ... */; // Type: rpl::producer<int>
// The pipe operator often handles the move implicitly for chained transformations.
auto strings = std::move(
ints // Explicit move is safer
) | rpl::map([](int value) {
return QString::number(value * 2);
}); // Emits strings like "0", "2", "4", ...
```
* `rpl::filter`: Emits only the values from a producer that satisfy a condition.
```cpp
auto ints = /* ... */; // Type: rpl::producer<int>
auto evenInts = std::move(
ints // Explicit move is safer
) | rpl::filter([](int value) {
return (value % 2 == 0);
}); // Emits only even numbers.
```
## Combining Producers
You can combine multiple producers into one:
* `rpl::combine`: Combines the latest values from multiple producers whenever *any* of them emits a new value. Requires all producers to have emitted at least one value initially.
While it produces a `std::tuple`, subsequent operators like `map`, `filter`, and `start_with_next` can automatically unpack this tuple into separate lambda arguments.
```cpp
auto countProducer = rpl::single(1); // Type: rpl::producer<int>
auto textProducer = rpl::single(u"hello"_q); // Type: rpl::producer<QString>
rpl::lifetime lifetime;
// rpl::combine takes producers by const-ref internally and duplicates,
// so move/duplicate is usually not strictly needed here unless you
// want to signal intent or manage the lifetime of p1/p2 explicitly.
auto combined = rpl::combine(
countProducer, // or rpl::duplicate(countProducer)
textProducer // or rpl::duplicate(textProducer)
);
// Starting the combined producer consumes it.
// The lambda receives unpacked arguments, not the tuple itself.
std::move(
combined
) | rpl::start_with_next([=](int count, const QString &text) {
// No need for std::get<0>(latest), etc.
qDebug() << "Combined: Count=" << count << ", Text=" << text;
}, lifetime);
// This also works with map, filter, etc.
std::move(
combined
) | rpl::filter([=](int count, const QString &text) {
return count > 0 && !text.isEmpty();
}) | rpl::map([=](int count, const QString &text) {
return text.repeated(count);
}) | rpl::start_with_next([=](const QString &result) {
qDebug() << "Mapped & Filtered: " << result;
}, lifetime);
```
* `rpl::merge`: Merges the output of multiple producers of the *same type* into a single producer. It emits a value whenever *any* of the source producers emits a value.
```cpp
auto sourceA = /* ... */; // Type: rpl::producer<QString>
auto sourceB = /* ... */; // Type: rpl::producer<QString>
// rpl::merge also duplicates internally.
auto merged = rpl::merge(sourceA, sourceB);
// Starting the merged producer consumes it.
std::move(
merged
) | rpl::start_with_next([=](QString &&value) {
// Receives values from either sourceA or sourceB as they arrive.
qDebug() << "Merged value: " << value;
}, lifetime);
```
## Key Concepts Summary
* Use `rpl::producer<Type, Error>` to represent streams of values.
* Manage subscription duration using `rpl::lifetime`.
* Pass `rpl::lifetime` to `rpl::start_...` methods.
* If `rpl::lifetime` is not passed, **store the returned lifetime** to keep the subscription active.
* Use operators like `| rpl::map`, `| rpl::filter` to transform streams.
* Use `rpl::combine` or `rpl::merge` to combine streams.
* When starting a chain (`std::move(producer) | rpl::map(...)`), explicitly move the initial producer.
* These functions typically duplicate their input producers internally.
* Starting a pipeline consumes the producer; use `

View file

@ -1,149 +0,0 @@
# Telegram Desktop UI Styling
## Style Definition Files
UI element styles (colors, fonts, paddings, margins, icons, etc.) are defined in `.style` files using a custom syntax. These files are located alongside the C++ source files they correspond to within specific UI component directories (e.g., `Telegram/SourceFiles/ui/chat/chat.style`).
Definitions from other `.style` files can be included using the `using` directive at the top of the file:
```style
using "ui/basic.style";
using "ui/widgets/widgets.style";
```
The central definition of named colors happens in `Telegram/SourceFiles/ui/colors.palette`. This file allows for theme generation and loading colors from various sources.
### Syntax Overview
1. **Built-in Types:** The syntax recognizes several base types inferred from the value assigned:
* `int`: Integer numbers (e.g., `lineHeight: 20;`)
* `bool`: Boolean values (e.g., `useShadow: true;`)
* `pixels`: Pixel values, ending with `px` (e.g., `borderWidth: 1px;`). Generated as `int` in C++.
* `color`: Named colors defined in `colors.palette` (e.g., `background: windowBg;`)
* `icon`: Defined inline using a specific syntax (see below). Generates `style::icon`.
* `margins`: Four pixel values for margins or padding. Requires `margins(top, right, bottom, left)` syntax (e.g., `margin: margins(10px, 5px, 10px, 5px);` or `padding: margins(8px, 8px, 8px, 8px);`). Generates `style::margins` (an alias for `QMargins`).
* `size`: Two pixel values for width and height (e.g., `iconSize: size(16px, 16px);`). Generates `style::size`.
* `point`: Two pixel values for x and y coordinates (e.g., `textPos: point(5px, 2px);`). Generates `style::point`.
* `align`: Alignment keywords (e.g., `textAlign: align(center);` or `iconAlign: align(left);`). Generates `style::align`.
* `font`: Font definitions (e.g., `font: font(14px semibold);`). Generates `style::font`.
* `double`: Floating point numbers (e.g., `disabledOpacity: 0.5;`)
*Note on Borders:* Borders are typically defined using multiple fields like `border: pixels;` (for width) and `borderFg: color;` (for color), rather than a single CSS-like property.
2. **Structure Definition:** You can define complex data structures directly within the `.style` file:
```style
MyButtonStyle { // Defines a structure named 'MyButtonStyle'
textPadding: margins; // Field 'textPadding' expects margins type
icon: icon; // Field 'icon' of type icon
height: pixels; // Field 'height' of type pixels
}
```
This generates a `struct MyButtonStyle { ... };` inside the `namespace style`. Fields will have corresponding C++ types (`style::margins`, `style::icon`, `int`).
3. **Variable Definition & Inheritance:** Variables are defined using `name: value;` or `groupName { ... }`. They can be of built-in types or custom structures. Structures can be initialized inline or inherit from existing variables.
**Icon Definition Syntax:** Icons are defined inline using the `icon{...}` syntax. The generator probes for `.svg` files or `.png` files (including `@2x`, `@3x` variants) based on the provided path stem.
```style
// Single-part icon definition:
myIconSearch: icon{{ "gui/icons/search", iconColor }};
// Multi-part icon definition (layers drawn bottom-up):
myComplexIcon: icon{
{ "gui/icons/background", iconBgColor },
{ "gui/icons/foreground", iconFgColor }
};
// Icon with path modifiers (PNG only for flips, SVG only for size):
myFlippedIcon: icon{{ "gui/icons/arrow-flip_horizontal", arrowColor }};
myResizedIcon: icon{{ "gui/icons/logo-128x128", logoColor }}; // Forces 128x128 for SVG
```
**Other Variable Examples:**
```style
// Simple variables
buttonHeight: 30px;
activeButtonColor: buttonBgActive; // Named color from colors.palette
// Variable of a custom structure type, initialized inline
defaultButton: MyButtonStyle {
textPadding: margins(10px, 15px, 10px, 15px); // Use margins(...) syntax
icon: myIconSearch; // Assign the previously defined icon variable
height: buttonHeight; // Reference another variable
}
// Another variable inheriting from 'defaultButton' and overriding/adding fields
primaryButton: MyButtonStyle(defaultButton) {
icon: myComplexIcon; // Override icon with the multi-part one
backgroundColor: activeButtonColor; // Add a field not in MyButtonStyle definition
}
// Style group (often used for specific UI elements)
chatInput { // Example using separate border properties and explicit padding
border: 1px; // Border width
borderFg: defaultInputFieldBorder; // Border color (named color)
padding: margins(5px, 10px, 5px, 10px); // Use margins(...) syntax for padding field
backgroundColor: defaultChatBg; // Background color
}
```
## Code Generation
A code generation tool processes these `.style` files and `colors.palette` to create C++ objects.
- The `using` directives resolve dependencies between `.style` files.
- Custom structure definitions (like `MyButtonStyle`) generate corresponding `struct MyButtonStyle { ... };` within the `namespace style`.
- Style variables/groups (like `defaultButton`, `primaryButton`, `chatInput`) are generated as objects/structs within the `st` namespace (e.g., `st::defaultButton`, `st::primaryButton`, `st::chatInput`). These generated structs contain members corresponding to the fields defined in the `.style` file.
- Color objects are generated into the `st` namespace as well, based on their names in `colors.palette` (e.g., `st::windowBg`, `st::buttonBgActive`).
- The generated header files for styles are placed in the `Telegram/SourceFiles/styles/` directory with a `style_` prefix (e.g., `styles/style_widgets.h` for `ui/widgets/widgets.style`). You include them like `#include "styles/style_widgets.h"`.
Generated C++ types correspond to the `.style` types: `style::color`, `style::font`, `style::margins` (used for both `margin:` and `padding:` fields), `style::icon`, `style::size`, `style::point`, `style::align`, and `int` or `bool` for simple types.
## Style Usage in Code
Styles are applied in C++ code by referencing the generated `st::...` objects and their members.
```cpp
// Example: Including the generated style header
#include "styles/style_widgets.h" // For styles defined in ui/widgets/widgets.style
// ... inside some UI class code ...
// Accessing members of a generated style struct
int height = st::primaryButton.height; // Accessing the 'height' field (pixels -> int)
const style::icon &icon = st::primaryButton.icon; // Accessing the 'icon' field (st::myComplexIcon)
style::margins padding = st::primaryButton.textPadding; // Accessing 'textPadding'
style::color bgColor = st::primaryButton.backgroundColor; // Accessing the color (st::activeButtonColor)
// Applying styles (conceptual examples)
myButton->setIcon(st::primaryButton.icon);
myButton->setHeight(st::primaryButton.height);
myButton->setPadding(st::primaryButton.textPadding);
myButton->setBackgroundColor(st::primaryButton.backgroundColor);
// Using styles directly in painting
void MyWidget::paintEvent(QPaintEvent *e) {
Painter p(this);
p.fillRect(rect(), st::chatInput.backgroundColor); // Use color from chatInput style
// Border painting requires width and color
int borderWidth = st::chatInput.border; // Access border width (pixels -> int)
style::color borderColor = st::chatInput.borderFg; // Access border color
if (borderWidth > 0) {
p.setPen(QPen(borderColor, borderWidth));
// Adjust rect for pen width if needed before drawing
p.drawRect(rect().adjusted(borderWidth / 2, borderWidth / 2, -borderWidth / 2, -borderWidth / 2));
}
// Access padding (style::margins)
style::margins inputPadding = st::chatInput.padding;
// ... use inputPadding.top(), inputPadding.left() etc. for content layout ...
}
```
**Key Points:**
* Styles are defined in `.style` files next to their corresponding C++ source files.
* `using "path/to/other.style";` includes definitions from other style files.
* Named colors are defined centrally in `ui/colors.palette`.
* `.style` syntax supports built-in types (like `pixels`, `color`, `margins`, `point`, `size`, `align`, `font`, `double`), custom structure definitions (`Name { field: type; ... }`), variable definitions (`name: value;`), and inheritance (`child: Name(parent) { ... }`).
* Values must match the expected type (e.g., fields declared as `margins` type, like `margin:` or `padding:`, require `margins(...)` syntax). Borders are typically set via separate `border: pixels;` and `borderFg: color;` fields.
* Icons are defined inline using `name: icon{{ "path_stem", color }};` or `name: icon{ { "path1", c1 }, ... };` syntax, with optional path modifiers.
* Code generation creates `struct` definitions in the `style` namespace for custom types and objects/structs in the `st` namespace for defined variables/groups.
* Generated headers are in `styles/` with a `style_` prefix and must be included.
* Access style properties via the generated `st::` objects (e.g., `st::primaryButton.height`, `st::chatInput.backgroundColor`).

View file

@ -1,2 +0,0 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
Telegram/ThirdParty/

View file

@ -1,32 +0,0 @@
{
"name": "CentOS",
"image": "tdesktop:centos_env",
"customizations": {
"vscode": {
"settings": {
"C_Cpp.intelliSenseEngine": "disabled",
"clangd.arguments": [
"--compile-commands-dir=${workspaceFolder}/out"
],
"cmake.generator": "Ninja Multi-Config",
"cmake.buildDirectory": "${workspaceFolder}/out"
},
"extensions": [
"ms-vscode.cpptools-extension-pack",
"llvm-vs-code-extensions.vscode-clangd",
"TheQtCompany.qt",
"ms-python.python",
"ms-azuretools.vscode-docker",
"eamodio.gitlens"
]
}
},
"capAdd": [
"SYS_PTRACE"
],
"securityOpt": [
"seccomp=unconfined"
],
"workspaceMount": "source=${localWorkspaceFolder},target=/usr/src/tdesktop,type=bind,consistency=cached",
"workspaceFolder": "/usr/src/tdesktop"
}

BIN
.github/AyuChan.png vendored

Binary file not shown.

Before

(image error) Size: 13 KiB

View file

@ -1,6 +1,6 @@
# Contributing
This document describes how you can contribute to AyuGram Desktop.
This document describes how you can contribute to Telegram Desktop. Please read it carefully.
**Table of Contents**
@ -17,15 +17,20 @@ This document describes how you can contribute to AyuGram Desktop.
## What contributions are accepted
We highly appreciate your contributions in the matter of fixing bugs and optimizing the AyuGram Desktop source code and its documentation. In case of fixing the existing user experience please push to your fork and [submit a pull request][pr].
We highly appreciate your contributions in the matter of fixing bugs and optimizing the Telegram Desktop source code and its documentation. In case of fixing the existing user experience please push to your fork and [submit a pull request][pr].
If you have a translations-related contribution, check out [our Crowdin][translate].
Wait for us. We try to review your pull requests as fast as possible.
If we find issues with your pull request, we may suggest some changes and improvements.
Highly appreciated feature implementations from [Android app][android_repo].
Unfortunately we **do not merge** any pull requests that have new feature implementations, translations to new languages and those which introduce any new user interface elements.
If you have a translations-related contribution, check out [Translations platform][translate].
Telegram Desktop is not a standalone application but a part of [Telegram project][telegram], so all the decisions about the features, languages, user experience, user interface and the design are made inside Telegram team, often according to some roadmap which is not public.
## Build instructions
See [folder with instructions][build_instructions] for details on the various build
See the [README.md][build_instructions] for details on the various build
environments.
## Pull upstream changes into your fork regularly
@ -34,7 +39,7 @@ Telegram Desktop is advancing quickly. It is therefore critical that you pull up
To pull in upstream changes:
git remote add upstream https://github.com/AyuGram/AyuGramDesktop.git
git remote add upstream https://github.com/telegramdesktop/tdesktop.git
git fetch upstream master
Check the log to be sure that you actually want the changes, before merging:
@ -53,7 +58,7 @@ For more info, see [GitHub Help][help_fork_repo].
## How to get your pull request accepted
We want to improve AyuGram Desktop with your contributions. But we also want to provide a stable experience for our users and the community. Follow these rules and you should succeed without a problem!
We want to improve Telegram Desktop with your contributions. But we also want to provide a stable experience for our users and the community. Follow these rules and you should succeed without a problem!
### Keep your pull requests limited to a single issue
@ -107,8 +112,7 @@ Before you submit a pull request, please test your changes. Verify that Telegram
[help_fork_repo]: https://help.github.com/articles/fork-a-repo/
[help_change_commit_message]: https://help.github.com/articles/changing-a-commit-message/
[commit_message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
[pr]: https://github.com/AyuGram/AyuGramDesktop/compare
[build_instructions]: https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs
[pr]: https://github.com/telegramdesktop/tdesktop/compare
[build_instructions]: https://github.com/telegramdesktop/tdesktop/blob/master/README.md#build-instructions
[closing-issues-via-commit-messages]: https://help.github.com/articles/closing-issues-via-commit-messages/
[translate]: https://crowdin.com/project/ayugram
[android_repo]: https://github.com/AyuGram/AyuGram4A
[translate]: https://translations.telegram.org

View file

@ -5,7 +5,7 @@ body:
- type: markdown
attributes:
value: |
Thanks for reporting issues of AyuGram Desktop!
Thanks for reporting issues of Telegram Desktop!
To make it easier for us to help you please enter detailed information below.
- type: textarea
@ -39,9 +39,12 @@ body:
required: true
- type: input
attributes:
label: Version of AyuGram Desktop
label: Version of Telegram Desktop
description: >
**Don't use 'latest'**, specify actual version.
Please note we don't support versions from Linux distro repositories.
If you need support for these versions, **please contact your distro maintainer**
or your distro bugtracker.
**Don't use 'latest'**, specify actual version, **that's a reason to close your issue**.
validations:
required: true
- type: dropdown
@ -49,7 +52,11 @@ body:
label: Installation source
multiple: false
options:
- Binary from GitHub / official Telegram source
- Static binary from official website
- Microsoft Store
- Mac App Store
- Flatpak
- Snap
- Other (unofficial) source
validations:
required: true
@ -58,7 +65,9 @@ body:
label: Crash ID
description: >
If you're reporting a crash, please enter the crash ID from the crash reporter
opening on the next launch after crash.
opening on the next launch after crash. **You have to enable beta versions
installation in Settings -> Advanced for the reporter to appear.**
You don't have to wait for a beta version to arrive.
- type: textarea
attributes:
label: Logs

View file

@ -1,6 +1,6 @@
blank_issues_enabled: false
contact_links:
- name: Platform-wide issue
- name: API issue
url: https://bugs.telegram.org
about: Any bug report or feature request affecting more than only Telegram Desktop.
- name: Issue of other client

Binary file not shown.

Before

(image error) Size: 28 KiB

Binary file not shown.

Before

(image error) Size: 58 KiB

Binary file not shown.

Before

(image error) Size: 30 KiB

Binary file not shown.

Before

(image error) Size: 13 KiB

View file

@ -1,6 +0,0 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View file

@ -0,0 +1,16 @@
name: Copyright year updater.
on:
repository_dispatch:
types: ["Restart copyright_year_updater workflow."]
schedule:
# At 03:00 on January 1.
- cron: "0 3 1 1 *"
jobs:
Copyright-year:
runs-on: ubuntu-latest
steps:
- uses: desktop-app/action_code_updater@master
with:
type: "license-year"

45
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Docker.
on:
push:
paths:
- '.github/workflows/docker.yml'
- 'Telegram/build/docker/centos_env/**'
pull_request:
paths:
- '.github/workflows/docker.yml'
- 'Telegram/build/docker/centos_env/**'
jobs:
docker:
name: Ubuntu
runs-on: ubuntu-latest
env:
IMAGE_TAG: ghcr.io/${{ github.repository }}/centos_env:latest
steps:
- name: Clone.
uses: actions/checkout@v3.1.0
with:
submodules: recursive
- name: First set up.
run: |
sudo apt update
curl -sSL https://install.python-poetry.org | python3 -
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c
- name: Docker image build.
run: |
cd Telegram/build/docker/centos_env
poetry install
DEBUG= LTO= poetry run gen_dockerfile | DOCKER_BUILDKIT=1 docker build -t $IMAGE_TAG -
- name: Push the Docker image.
if: ${{ github.ref_name == github.event.repository.default_branch }}
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker push $IMAGE_TAG

14
.github/workflows/issue_closer.yml vendored Normal file
View file

@ -0,0 +1,14 @@
name: Issue closer.
on:
issues:
types: opened
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Process an issue.
uses: desktop-app/action_issue_closer@master
with:
token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -41,37 +41,46 @@ on:
jobs:
linux:
name: Rocky Linux 8
name: CentOS 7
runs-on: ubuntu-latest
container:
image: ghcr.io/${{ github.repository }}/centos_env
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
defaults:
run:
shell: scl enable rh-python38 -- scl enable llvm-toolset-7.0 -- scl enable devtoolset-10 -- bash --noprofile --norc -eo pipefail {0}
strategy:
matrix:
defines:
- ""
- "DESKTOP_APP_DISABLE_X11_INTEGRATION"
- "DESKTOP_APP_DISABLE_WAYLAND_INTEGRATION"
env:
UPLOAD_ARTIFACT: "true"
UPLOAD_ARTIFACT: "false"
steps:
- name: Get repository name.
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v4
uses: actions/checkout@v3.1.0
with:
submodules: recursive
path: ${{ env.REPO_NAME }}
- name: First set up.
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
docker pull ghcr.io/$GITHUB_REPOSITORY/centos_env
docker tag ghcr.io/$GITHUB_REPOSITORY/centos_env tdesktop:centos_env
gcc --version
ln -s /usr/src/Libraries
- name: Telegram Desktop build.
run: |
cd $REPO_NAME
cd $REPO_NAME/Telegram
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
@ -82,22 +91,18 @@ jobs:
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
fi
docker run --rm \
-u $(id -u) \
-v $PWD:/usr/src/tdesktop \
-e CONFIG=Debug \
tdesktop:centos_env \
/usr/src/tdesktop/Telegram/build/docker/centos_env/build.sh \
./configure.sh \
-D CMAKE_C_FLAGS_DEBUG="" \
-D CMAKE_CXX_FLAGS_DEBUG="" \
-D CMAKE_C_FLAGS="-Werror" \
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_EXE_LINKER_FLAGS="-s" \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
cmake --build ../out --config Debug --parallel
- name: Check.
run: |
filePath="$REPO_NAME/out/Debug/Telegram"
@ -116,8 +121,8 @@ jobs:
run: |
cd $REPO_NAME/out/Debug
mkdir artifact
mv {Telegram,Updater} artifact/
- uses: actions/upload-artifact@v4
mv Telegram artifact/
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
with:

15
.github/workflows/lock.yml vendored Normal file
View file

@ -0,0 +1,15 @@
name: 'Lock Threads'
on:
schedule:
- cron: '0 0 * * *'
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v3
with:
github-token: ${{ github.token }}
issue-inactive-days: 45
pr-inactive-days: 45

138
.github/workflows/mac.yml vendored Normal file
View file

@ -0,0 +1,138 @@
name: MacOS.
on:
push:
paths-ignore:
- 'docs/**'
- '**.md'
- 'changelog.txt'
- 'LEGAL'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/mac.yml'
- 'lib/xdg/**'
- 'snap/**'
- 'Telegram/build/docker/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'
- 'Telegram/SourceFiles/platform/linux/**'
- 'Telegram/configure.bat'
pull_request:
paths-ignore:
- 'docs/**'
- '**.md'
- 'changelog.txt'
- 'LEGAL'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/mac.yml'
- 'lib/xdg/**'
- 'snap/**'
- 'Telegram/build/docker/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'
- 'Telegram/SourceFiles/platform/linux/**'
- 'Telegram/configure.bat'
jobs:
macos:
name: MacOS
runs-on: macos-12
strategy:
matrix:
defines:
- ""
env:
UPLOAD_ARTIFACT: "false"
ONLY_CACHE: "false"
PREPARE_PATH: "Telegram/build/prepare/prepare.py"
steps:
- name: Get repository name.
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Clone.
uses: actions/checkout@v3.1.0
with:
submodules: recursive
path: ${{ env.REPO_NAME }}
- name: First set up.
run: |
sudo chown -R `whoami`:admin /usr/local/share
brew install automake ninja pkg-config
# Disable spotlight.
sudo mdutil -a -i off
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
- name: ThirdParty cache.
id: cache-third-party
uses: actions/cache@v3.0.11
with:
path: ThirdParty
key: ${{ runner.OS }}-third-party-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
restore-keys: ${{ runner.OS }}-third-party-
- name: Libraries cache.
id: cache-libs
uses: actions/cache@v3.0.11
with:
path: Libraries
key: ${{ runner.OS }}-libs-${{ hashFiles(format('{0}/{1}', env.REPO_NAME, env.PREPARE_PATH)) }}
restore-keys: ${{ runner.OS }}-libs-
- name: Libraries.
run: |
./$REPO_NAME/Telegram/build/prepare/mac.sh skip-release silent
- name: Free up some disk space.
run: |
cd Libraries
find . -iname "*.dir" -exec rm -rf {} || true \;
- name: Telegram Desktop build.
if: env.ONLY_CACHE == 'false'
run: |
cd $REPO_NAME/Telegram
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
echo Define from matrix: $DEFINE
echo "ARTIFACT_NAME=Telegram_${{ matrix.defines }}" >> $GITHUB_ENV
else
echo "ARTIFACT_NAME=Telegram" >> $GITHUB_ENV
fi
./configure.sh \
-D CMAKE_C_FLAGS="-Werror" \
-D CMAKE_CXX_FLAGS="-Werror" \
-D CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=NO \
-D TDESKTOP_API_TEST=ON \
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF \
$DEFINE
cd ../out
xcoderun='xcodebuild build -project Telegram.xcodeproj -scheme Telegram -destination "platform=macOS,arch=x86_64" -configuration Debug'
bash -c "$xcoderun" || bash -c "$xcoderun" || bash -c "$xcoderun"
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'
run: |
cd $REPO_NAME/out/Debug
mkdir artifact
mv Telegram.app artifact/
mv Updater artifact/
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
with:
name: ${{ env.ARTIFACT_NAME }}
path: ${{ env.REPO_NAME }}/out/Debug/artifact/

35
.github/workflows/master_updater.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: Master branch updater.
on:
release:
types: released
jobs:
updater:
runs-on: ubuntu-latest
env:
SKIP: "0"
to_branch: "master"
steps:
- uses: actions/checkout@v3.1.0
if: env.SKIP == '0'
- name: Push the code to the master branch.
if: env.SKIP == '0'
run: |
token=${{ secrets.TOKEN_FOR_MASTER_UPDATER }}
if [ -z "${token}" ]; then
echo "Token is unset. Nothing to do."
exit 0
fi
url=https://x-access-token:$token@github.com/$GITHUB_REPOSITORY
latest_tag=$(git describe --tags --abbrev=0)
echo "Latest tag: $latest_tag"
git remote set-url origin $url
git remote -v
git checkout master
git merge $latest_tag
git push origin HEAD:refs/heads/$to_branch
echo "Done!"

19
.github/workflows/no-response.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: No Response
# Both `issue_comment` and `scheduled` event types are required for this Action
# to work properly.
on:
issue_comment:
types: [created]
schedule:
- cron: '0 0 * * *'
jobs:
noResponse:
runs-on: ubuntu-latest
steps:
- uses: lee-dohm/no-response@v0.5.0
with:
token: ${{ github.token }}
# Label requiring a response
responseRequiredLabel: waiting for answer

83
.github/workflows/snap.yml vendored Normal file
View file

@ -0,0 +1,83 @@
name: Snap.
on:
push:
paths-ignore:
- 'docs/**'
- '**.md'
- 'changelog.txt'
- 'LEGAL'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/snap.yml'
- 'Telegram/build/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'
- 'Telegram/SourceFiles/platform/mac/**'
- 'Telegram/Telegram/**'
- 'Telegram/configure.bat'
- 'Telegram/Telegram.plist'
pull_request:
paths-ignore:
- 'docs/**'
- '**.md'
- 'changelog.txt'
- 'LEGAL'
- 'LICENSE'
- '.github/**'
- '!.github/workflows/snap.yml'
- 'Telegram/build/**'
- 'Telegram/Resources/uwp/**'
- 'Telegram/Resources/winrc/**'
- 'Telegram/SourceFiles/platform/win/**'
- 'Telegram/SourceFiles/platform/mac/**'
- 'Telegram/Telegram/**'
- 'Telegram/configure.bat'
- 'Telegram/Telegram.plist'
jobs:
snap:
name: Ubuntu
runs-on: ubuntu-20.04
env:
UPLOAD_ARTIFACT: "false"
steps:
- name: Clone.
uses: actions/checkout@v3.1.0
with:
fetch-depth: 0
submodules: recursive
- name: First set up.
run: |
sudo iptables -P FORWARD ACCEPT
sudo snap install --classic snapcraft
sudo usermod -aG lxd $USER
sudo snap run lxd init --auto
sudo snap run lxd waitready
- name: Free up some disk space.
uses: jlumbroso/free-disk-space@76866dbe54312617f00798d1762df7f43def6e5c
- name: Telegram Desktop snap build.
run: sg lxd -c 'snap run snapcraft -v'
- name: Move artifact.
if: env.UPLOAD_ARTIFACT == 'true'
run: |
artifact_name=$(echo telegram-desktop_*.snap)
echo "ARTIFACT_NAME=$artifact_name" >> $GITHUB_ENV
mkdir artifact
mv $artifact_name artifact
- uses: actions/upload-artifact@master
if: env.UPLOAD_ARTIFACT == 'true'
name: Upload artifact.
with:
name: ${{ env.ARTIFACT_NAME }}
path: artifact

25
.github/workflows/stale.yml vendored Normal file
View file

@ -0,0 +1,25 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5
with:
stale-issue-message: |
Hey there!
This issue was inactive for a long time and will be automatically closed in 30 days if there isn't any further activity. We therefore assume that the user has lost interest or resolved the problem on their own.
Don't worry though; if this is an error, let us know with a comment and we'll be happy to reopen the issue.
Thanks!
stale-issue-label: 'stale'
exempt-issue-labels: 'enhancement'
days-before-stale: 180
days-before-close: 30
days-before-pr-stale: -1
operations-per-run: 1000

View file

@ -0,0 +1,18 @@
name: User-agent updater.
on:
repository_dispatch:
types: ["Restart user_agent_updater workflow."]
schedule:
# At 00:00 on day-of-month 1.
- cron: "0 0 1 * *"
pull_request_target:
types: [closed]
jobs:
User-agent:
runs-on: ubuntu-latest
steps:
- uses: desktop-app/action_code_updater@master
with:
type: "user-agent"

View file

@ -1,6 +1,7 @@
name: Windows.
on:
workflow_dispatch:
push:
paths-ignore:
- 'docs/**'
@ -42,12 +43,11 @@ jobs:
windows:
name: Windows
runs-on: windows-latest
runs-on: windows-2022
strategy:
matrix:
arch: [Win32, x64]
generator: ["", "Ninja Multi-Config"]
arch: [Win32]
env:
UPLOAD_ARTIFACT: "true"
@ -61,21 +61,24 @@ jobs:
steps:
- name: Prepare directories.
run: |
mkdir %userprofile%\TBuild\Libraries
mkdir %userprofile%\TBuild
mklink /d %GITHUB_WORKSPACE%\TBuild %userprofile%\TBuild
echo TBUILD=%GITHUB_WORKSPACE%\TBuild>>%GITHUB_ENV%
mkdir %userprofile%\TBuild Libraries
mklink /d %userprofile%\TBuild\Libraries %GITHUB_WORKSPACE%\Libraries
- name: Get repository name.
shell: bash
run: echo "REPO_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- uses: ilammy/msvc-dev-cmd@v1.13.0
- uses: ilammy/msvc-dev-cmd@v1.12.0
name: Native Tools Command Prompt.
with:
arch: ${{ matrix.arch }}
- name: Clone.
uses: actions/checkout@v4
uses: actions/checkout@v3.1.0
with:
submodules: recursive
path: ${{ env.TBUILD }}\${{ env.REPO_NAME }}
@ -94,19 +97,11 @@ jobs:
nuget sources Disable -Name "Microsoft Visual Studio Offline Packages"
nuget sources Add -Source https://api.nuget.org/v3/index.json & exit 0
- name: ThirdParty cache.
id: cache-third-party
uses: actions/cache@v4
with:
path: ${{ env.TBUILD }}\ThirdParty
key: ${{ runner.OS }}-${{ matrix.arch }}-third-party-${{ env.CACHE_KEY }}
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-third-party-
- name: Libraries cache.
id: cache-libs
uses: actions/cache@v4
uses: actions/cache@v3.0.11
with:
path: ${{ env.TBUILD }}\Libraries
path: Libraries
key: ${{ runner.OS }}-${{ matrix.arch }}-libs-${{ env.CACHE_KEY }}
restore-keys: ${{ runner.OS }}-${{ matrix.arch }}-libs-
@ -114,48 +109,23 @@ jobs:
env:
GYP_MSVS_OVERRIDE_PATH: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\'
GYP_MSVS_VERSION: 2022
run: |
cd %TBUILD%
%REPO_NAME%\Telegram\build\prepare\win.bat skip-release silent
run: '%TBUILD%\%REPO_NAME%\Telegram\build\prepare\win.bat skip-release silent'
- name: Read configuration matrix.
- name: Read defines.
shell: bash
run: |
ARTIFACT_NAME="Telegram"
ARCH=""
if [ -n "${{ matrix.arch }}" ]; then
case "${{ matrix.arch }}" in
Win32) ARCH="x86";;
*) ARCH="${{ matrix.arch }}";;
esac
echo "Architecture from matrix: $ARCH"
ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.arch }}"
fi
GENERATOR=""
if [ -n "${{ matrix.generator }}" ]; then
GENERATOR="-G \"${{ matrix.generator }}\""
echo "Generator from matrix: $GENERATOR"
ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.generator }}"
fi
echo "TDESKTOP_BUILD_GENERATOR=$GENERATOR" >> $GITHUB_ENV
[ -n "$GENERATOR" ] && ARCH=""
echo "TDESKTOP_BUILD_ARCH=$ARCH" >> $GITHUB_ENV
DEFINE=""
if [ -n "${{ matrix.defines }}" ]; then
DEFINE="-D ${{ matrix.defines }}=ON"
echo "Define from matrix: $DEFINE"
ARTIFACT_NAME="${ARTIFACT_NAME}_${{ matrix.defines }}"
echo "ARTIFACT_NAME=Telegram_${{ matrix.arch }}_${{ matrix.defines }}" >> $GITHUB_ENV
else
echo "ARTIFACT_NAME=Telegram_${{ matrix.arch }}" >> $GITHUB_ENV
fi
echo "TDESKTOP_BUILD_DEFINE=$DEFINE" >> $GITHUB_ENV
echo "ARTIFACT_NAME=$ARTIFACT_NAME" >> $GITHUB_ENV
API="-D TDESKTOP_API_TEST=ON"
if [ $GITHUB_REF == 'refs/heads/nightly' ]; then
if [ ${{ github.ref == 'refs/heads/nightly' }} ]; then
echo "Use the open credentials."
API="-D TDESKTOP_API_ID=611335 -D TDESKTOP_API_HASH=d524b414d21f4d37f08684c1df41ac9c"
fi
@ -163,7 +133,6 @@ jobs:
- name: Free up some disk space.
run: |
cd %TBUILD%
del /S Libraries\*.pdb
del /S Libraries\*.pch
del /S Libraries\*.obj
@ -174,28 +143,50 @@ jobs:
cd %TBUILD%\%REPO_NAME%\Telegram
call configure.bat ^
%TDESKTOP_BUILD_GENERATOR% ^
%TDESKTOP_BUILD_ARCH% ^
${{ matrix.arch }} ^
%TDESKTOP_BUILD_API% ^
-D CMAKE_C_FLAGS="/WX" ^
-D CMAKE_CXX_FLAGS="/WX" ^
-D DESKTOP_APP_DISABLE_AUTOUPDATE=OFF ^
-D DESKTOP_APP_DISABLE_CRASH_REPORTS=OFF ^
-D DESKTOP_APP_NO_PDB=ON ^
%TDESKTOP_BUILD_DEFINE%
%TDESKTOP_BUILD_DEFINE% ^
-DCMAKE_SYSTEM_VERSION=%SDK%
cmake --build ..\out --config Debug --parallel
cd ..\out
msbuild -m Telegram.sln /p:Configuration=Debug,Platform=${{ matrix.arch }},DebugSymbols=false,DebugType=none
- name: Move artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
if: (env.UPLOAD_ARTIFACT == 'true') || ${{ github.ref == 'refs/heads/nightly' }}
run: |
set OUT=%TBUILD%\%REPO_NAME%\out\Debug
mkdir artifact
move %OUT%\Telegram.exe artifact/
move %OUT%\Updater.exe artifact/
- uses: actions/upload-artifact@v4
move %TBUILD%\%REPO_NAME%\out\Debug\AyuGram.exe artifact/
- uses: actions/upload-artifact@master
name: Upload artifact.
if: (env.UPLOAD_ARTIFACT == 'true') || (github.ref == 'refs/heads/nightly')
if: (env.UPLOAD_ARTIFACT == 'true') || ${{ github.ref == 'refs/heads/nightly' }}
with:
name: ${{ env.ARTIFACT_NAME }}
path: artifact\
release:
name: Release
needs: [windows]
runs-on: ubuntu-latest
strategy:
fail-fast: true
steps:
- uses: actions/checkout@v2
- name: Merge Releases
uses: actions/download-artifact@v2
with:
name: Telegram_Win32
path: artifact
- name: Upload release
uses: Hs1r1us/Release-AIO@v1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# The name of the tag
tag_name: prelease-${{ github.run_number }}
prerelease: true
release_name: Ayugram CI
asset_files: './artifact'

20
.github/workflows/winget.yml vendored Normal file
View file

@ -0,0 +1,20 @@
name: Publish to WinGet
on:
release:
types: [released, prereleased]
jobs:
publish:
runs-on: windows-latest # action can only be run on windows
steps:
- if: github.event.action == 'released'
uses: telegramdesktop/winget-releaser@main
with:
identifier: Telegram.TelegramDesktop
installers-regex: 't(setup|portable).*(exe|zip)$'
token: ${{ secrets.WINGET_TOKEN }}
- if: github.event.action == 'prereleased'
uses: telegramdesktop/winget-releaser@main
with:
identifier: Telegram.TelegramDesktop.Beta
installers-regex: 't(setup|portable).*(exe|zip)$'
token: ${{ secrets.WINGET_TOKEN }}

5
.gitignore vendored
View file

@ -18,8 +18,6 @@ Release/
*.xcodeproj
ipch/
.vs/
.vscode/
.cache/
/Telegram/log.txt
/Telegram/data
@ -52,6 +50,3 @@ stage
*.*~
.idea/
cmake-build-debug/
# Project specific
Telegram/SourceFiles/_other/packer_private.h

22
.gitmodules vendored
View file

@ -3,7 +3,7 @@
url = https://github.com/telegramdesktop/libtgvoip
[submodule "Telegram/ThirdParty/GSL"]
path = Telegram/ThirdParty/GSL
url = https://github.com/Microsoft/GSL.git
url = https://github.com/desktop-app/GSL.git
[submodule "Telegram/ThirdParty/xxHash"]
path = Telegram/ThirdParty/xxHash
url = https://github.com/Cyan4973/xxHash.git
@ -24,7 +24,7 @@
url = https://github.com/desktop-app/lib_base.git
[submodule "Telegram/codegen"]
path = Telegram/codegen
url = https://github.com/AyuGram/codegen.git
url = https://github.com/desktop-app/codegen.git
[submodule "Telegram/lib_ui"]
path = Telegram/lib_ui
url = https://github.com/AyuGram/lib_ui.git
@ -58,6 +58,9 @@
[submodule "Telegram/ThirdParty/range-v3"]
path = Telegram/ThirdParty/range-v3
url = https://github.com/ericniebler/range-v3.git
[submodule "Telegram/ThirdParty/fcitx-qt5"]
path = Telegram/ThirdParty/fcitx-qt5
url = https://github.com/fcitx/fcitx-qt5.git
[submodule "Telegram/ThirdParty/nimf"]
path = Telegram/ThirdParty/nimf
url = https://github.com/hamonikr/nimf.git
@ -82,6 +85,12 @@
[submodule "Telegram/ThirdParty/dispatch"]
path = Telegram/ThirdParty/dispatch
url = https://github.com/apple/swift-corelibs-libdispatch
[submodule "Telegram/ThirdParty/plasma-wayland-protocols"]
path = Telegram/ThirdParty/plasma-wayland-protocols
url = https://github.com/KDE/plasma-wayland-protocols.git
[submodule "Telegram/ThirdParty/wayland-protocols"]
path = Telegram/ThirdParty/wayland-protocols
url = https://github.com/gitlab-freedesktop-mirrors/wayland-protocols.git
[submodule "Telegram/ThirdParty/kimageformats"]
path = Telegram/ThirdParty/kimageformats
url = https://github.com/KDE/kimageformats.git
@ -91,9 +100,6 @@
[submodule "Telegram/ThirdParty/cld3"]
path = Telegram/ThirdParty/cld3
url = https://github.com/google/cld3.git
[submodule "Telegram/ThirdParty/libprisma"]
path = Telegram/ThirdParty/libprisma
url = https://github.com/desktop-app/libprisma.git
[submodule "Telegram/ThirdParty/xdg-desktop-portal"]
path = Telegram/ThirdParty/xdg-desktop-portal
url = https://github.com/flatpak/xdg-desktop-portal.git
[submodule "Telegram/ThirdParty/wayland"]
path = Telegram/ThirdParty/wayland
url = https://github.com/gitlab-freedesktop-mirrors/wayland.git

View file

@ -10,9 +10,8 @@ if (APPLE)
else()
cmake_minimum_required(VERSION 3.16)
endif()
if (POLICY CMP0149)
cmake_policy(SET CMP0149 NEW)
endif()
cmake_policy(SET CMP0076 NEW)
cmake_policy(SET CMP0091 NEW)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@ -20,18 +19,16 @@ include(cmake/validate_special_target.cmake)
include(cmake/version.cmake)
desktop_app_parse_version(Telegram/build/version)
set(project_langs C CXX)
set(project_langs ASM C CXX)
if (APPLE)
list(APPEND project_langs OBJC OBJCXX)
elseif (LINUX)
list(APPEND project_langs ASM)
endif()
project(Telegram
LANGUAGES ${project_langs}
VERSION ${desktop_app_version_cmake}
DESCRIPTION "AyuGram Desktop"
HOMEPAGE_URL "https://ayugram.one"
DESCRIPTION "Official Telegram Desktop messenger"
HOMEPAGE_URL "https://desktop.telegram.org"
)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT Telegram)
@ -57,10 +54,17 @@ include(cmake/validate_d3d_compiler.cmake)
include(cmake/target_prepare_qrc.cmake)
include(cmake/options.cmake)
if (NOT DESKTOP_APP_USE_PACKAGED)
if (WIN32)
set(qt_version 5.15.9)
elseif (APPLE)
set(qt_version 6.3.2)
endif()
endif()
include(cmake/external/qt/package.cmake)
set(desktop_app_skip_libs
glibmm
variant
)

2
LEGAL
View file

@ -1,7 +1,7 @@
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
Copyright (c) 2014-2025 The Telegram Desktop Authors.
Copyright (c) 2014-2023 The Telegram Desktop Authors.
Telegram Desktop is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View file

@ -1,103 +0,0 @@
# AyuGram
![AyuGram Лого](.github/AyuGram.png) ![AyuChan](.github/AyuChan.png)
[ [English](README.md) | Русский ]
## Функции и Фишки
- Полный режим призрака (настраиваемый)
- История удалений и изменений сообщений
- Кастомизация шрифта
- Режим Стримера
- Локальный телеграм премиум
- Превью медиа и быстрая реакция при сильном нажатии на тачпад (macOS)
- Улучшенный вид
И многое другое. Посмотрите нашу [Документацию](https://docs.ayugram.one/desktop/) для более подробной информации.
<h3>
<details>
<summary>Скриншоты настроек</summary>
<img src='.github/demos/demo1.png' width='268'>
<img src='.github/demos/demo2.png' width='268'>
<img src='.github/demos/demo3.png' width='268'>
<img src='.github/demos/demo4.png' width='268'>
</details>
</h3>
## Установка
### Windows
#### Официальный вариант
Вы можете скачать готовый бинарный файл со вкладки [Releases](https://github.com/AyuGram/AyuGramDesktop/releases) или из
[Телеграм чата](https://t.me/ayugramchat/12788).
#### Winget
```bash
winget install RadolynLabs.AyuGramDesktop
```
#### Scoop
```bash
scoop bucket add extras
scoop install ayugram
```
#### Сборка вручную
Следуйте [официальному руководству](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-win-x64.md), если
вы хотите собрать AyuGram сами.
### macOS
Вы можете скачать подписанный пакет со вкладки [Releases](https://github.com/AyuGram/AyuGramDesktop/releases).
### Arch Linux
Вы можете установить `ayugram-desktop` из [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
### NixOS
Попробуйте [этот репозиторий](https://github.com/ayugram-port/ayugram-desktop).
### Любой другой Линукс дистрибутив
Следуйте [официальному руководству](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-linux.md).
### Примечания для Windows
Убедитесь что у вас присутствуют эти зависимости:
- C++ MFC latest (x86 & x64)
- C++ ATL latest (x86 & x64)
- последний Windows 11 SDK
## Пожертвования
Вам нравится использовать **AyuGram**? Оставьте нам чаевые!
[Здесь доступные варианты.](https://docs.ayugram.one/donate/)
## Использованные материалы
### Телеграм клиенты
- [Telegram Desktop](https://github.com/telegramdesktop/tdesktop)
- [Kotatogram](https://github.com/kotatogram/kotatogram-desktop)
- [64Gram](https://github.com/TDesktop-x64/tdesktop)
- [Forkgram](https://github.com/forkgram/tdesktop)
### Использованные библиотеки
- [JSON for Modern C++](https://github.com/nlohmann/json)
- [SQLite](https://github.com/sqlite/sqlite)
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
### Иконки
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)

View file

@ -1,104 +1,52 @@
# AyuGram
![AyuGram Logo](.github/AyuGram.png) ![AyuChan](.github/AyuChan.png)
[ English | [Русский](README-RU.md) ]
![AyuGram Logo](.github/AyuGram.png)
## Features
- Full ghost mode (flexible)
- Messages history
- Anti-recall
- Font customization
- Streamer mode
- Local Telegram Premium
- Media preview & quick reaction on force click (macOS)
- Enhanced appearance
- Disable read packets sending
- Disable online packets sending
- Disable typing & upload packets sending
- Auto offline
- Messages history (+ deleted ones)
- Using scheduled messages to keep offline
And many more. Check out our [Documentation](https://docs.ayugram.one/desktop/).
Technically, we have the **Ghost mode** starter pack.
<h3>
<details>
<summary>Preferences screenshots</summary>
<img src='.github/demos/demo1.png' width='268'>
<img src='.github/demos/demo2.png' width='268'>
<img src='.github/demos/demo3.png' width='268'>
<img src='.github/demos/demo4.png' width='268'>
</details>
</h3>
Also, we have a cool **purple icon**.
## Downloads
## Downloads? / FAQ?
### Windows
We have both **Windows** and **Linux** builds.
#### Official
Follow our [Telegram channel](https://t.me/ayugram1338). FAQ can be found here.
You can download prebuilt Windows binary from [Releases tab](https://github.com/AyuGram/AyuGramDesktop/releases) or from
the [Telegram topic](https://t.me/ayugramchat/12788).
## May I get banned?
#### Winget
Well, *you* **can't**, because you're just an ordinary user.
```bash
winget install RadolynLabs.AyuGramDesktop
```
## How to build
#### Scoop
Follow [official guide](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-win-x64.md).
```bash
scoop bucket add extras
scoop install ayugram
```
#### Self-built
Follow [official guide](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-win-x64.md) if you want to
build by yourself.
### macOS
You can download prebuilt macOS package from [Releases tab](https://github.com/AyuGram/AyuGramDesktop/releases).
### Arch Linux
You can install `ayugram-desktop` from [AUR](https://aur.archlinux.org/packages?O=0&K=ayugram).
### NixOS
See [this repository](https://github.com/ayugram-port/ayugram-desktop) for installation manual.
### Any other Linux distro
Follow the [official guide](https://github.com/AyuGram/AyuGramDesktop/blob/dev/docs/building-linux.md).
### Remarks for Windows
### Remarks
Make sure you have these components installed with VS Build Tools:
- C++ MFC latest (x86 & x64)
- C++ ATL latest (x86 & x64)
- latest Windows 11 SDK
## Donation
Enjoy using **AyuGram**? Consider sending us a tip!
[Here's available methods.](https://docs.ayugram.one/donate/)
## Credits
### Telegram clients
- [Telegram Desktop](https://github.com/telegramdesktop/tdesktop)
- [Kotatogram](https://github.com/kotatogram/kotatogram-desktop)
- [64Gram](https://github.com/TDesktop-x64/tdesktop)
- [Forkgram](https://github.com/forkgram/tdesktop)
### Libraries used
- [JSON for Modern C++](https://github.com/nlohmann/json)
- [SQLite](https://github.com/sqlite/sqlite)
- [sqlite_orm](https://github.com/fnc12/sqlite_orm)
### Icons
### Very special thanks to
- [Solar Icon Set](https://www.figma.com/community/file/1166831539721848736)
- [Solar Icon Set](https://solariconset.com/)
- [JSON for Modern C++](https://github.com/nlohmann/json)
- [BitConverter](https://github.com/YanjieHe/BitConverter)
- [Not Enough Standards](https://github.com/Alairion/not-enough-standards)

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

(image error) Size: 78 KiB

Binary file not shown.

Before

(image error) Size: 8.5 KiB

Binary file not shown.

Before

(image error) Size: 29 KiB

Binary file not shown.

Before

(image error) Size: 13 KiB

Binary file not shown.

Before

(image error) Size: 15 KiB

Binary file not shown.

Before

(image error) Size: 30 KiB

Binary file not shown.

Before

(image error) Size: 56 KiB

Binary file not shown.

Before

(image error) Size: 27 KiB

Binary file not shown.

Before

(image error) Size: 32 KiB

Binary file not shown.

Before

(image error) Size: 4 KiB

Binary file not shown.

Before

(image error) Size: 36 KiB

Binary file not shown.

Before

(image error) Size: 24 KiB

Binary file not shown.

Before

(image error) Size: 25 KiB

Binary file not shown.

Before

(image error) Size: 16 KiB

Binary file not shown.

Before

(image error) Size: 56 KiB

Binary file not shown.

Before

(image error) Size: 30 KiB

Binary file not shown.

Before

(image error) Size: 33 KiB

Binary file not shown.

Before

(image error) Size: 9.9 KiB

Binary file not shown.

Before

(image error) Size: 35 KiB

Binary file not shown.

Before

(image error) Size: 17 KiB

Binary file not shown.

Before

(image error) Size: 19 KiB

Binary file not shown.

Before

(image error) Size: 9.2 KiB

Binary file not shown.

Before

(image error) Size: 33 KiB

Binary file not shown.

Before

(image error) Size: 15 KiB

Binary file not shown.

Before

(image error) Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show more