Compare commits
47 Commits
Author | SHA1 | Date |
---|---|---|
LOUDO | 65e9d3246b | |
LOUDO | 3ba2511fea | |
LOUDO | 87ab15bdd2 | |
LOUDO | 355621f478 | |
LOUDO | aba510cf99 | |
LE LOUER Lucas | 3c37e4fd25 | |
LE LOUER Lucas | 44249feee9 | |
LE LOUER Lucas | b5043c201a | |
LE LOUER Lucas | a6f2fcba97 | |
LE LOUER Lucas | 1d354e5d93 | |
LE LOUER Lucas | 77a555f97a | |
LE LOUER Lucas | 02d4212642 | |
LE LOUER Lucas | 61cf70ab50 | |
LE LOUER Lucas | bb230d7f95 | |
LE LOUER Lucas | 544e007b57 | |
LE LOUER Lucas | 43fe2edd34 | |
LOUDO | 3abb45d6d4 | |
LOUDO | 3b517f2bff | |
LOUDO | 60ec33923d | |
LOUDO | 3be8937c79 | |
LOUDO | 1b4cefce30 | |
LOUDO | 35a4a75f75 | |
LLL | c7827a40a0 | |
LE LOUER Lucas | f8f24790b2 | |
LE LOUER Lucas | 0ddf320d18 | |
LE LOUER Lucas | fd1c52972a | |
LE LOUER Lucas | 073d79bc8b | |
LE LOUER Lucas | a1de801420 | |
LE LOUER Lucas | f21696d2f5 | |
LE LOUER Lucas | 5b0f6ea8da | |
LE LOUER Lucas | 2d217703e8 | |
LE LOUER Lucas | 96e6ded1c9 | |
LE LOUER Lucas | f354e49b4d | |
LE LOUER Lucas | f772b06ef3 | |
LE LOUER Lucas | 13f1717f24 | |
LE LOUER Lucas | 8b3a97d146 | |
LE LOUER Lucas | 2b84143194 | |
LE LOUER Lucas | e7a5bf7e4f | |
LE LOUER Lucas | ea5fb5f795 | |
LE LOUER Lucas | b4dc0447d9 | |
LE LOUER Lucas | 91d8f71c78 | |
LE LOUER Lucas | 77b96bfd51 | |
LE LOUER Lucas | 4ecb450219 | |
LE LOUER Lucas | 8e733ea760 | |
LE LOUER Lucas | 48899ccea2 | |
LE LOUER Lucas | 2654d774c5 | |
LE LOUER Lucas | c1dc955780 |
|
@ -1,5 +0,0 @@
|
|||
data
|
||||
.idea
|
||||
__pycache__
|
||||
.venv
|
||||
updates
|
674
LICENSE.md
|
@ -1,674 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
87
README.md
|
@ -1,87 +0,0 @@
|
|||
# PyMacroRecord
|
||||
PyMacroRecord is a completely free Macro Recorder, coded with Python.
|
||||
|
||||
# Overview
|
||||
PyMacroRecord works with a GUI made using tkinter, making it easier for users to interact with it.
|
||||
![image](https://github.com/LOUDO56/PyMacroRecord/assets/117168736/2a1b2d0e-d950-40ad-84e2-971464058664)
|
||||
|
||||
# Features
|
||||
- Very Easy to use
|
||||
- Free. No limitations. No "premium" purchase.
|
||||
- You can set an infinite amount of repeats.
|
||||
- You can change the speed of your record.
|
||||
- You can save your record.
|
||||
- You can load your record.
|
||||
- You can share your record with other people.
|
||||
- Universal Files (work with .json).
|
||||
- After-playback options, e.g., Standby or shutdown computer.
|
||||
- Can choose from recording mouse movement, click and keyboard input
|
||||
- Custom Hotkey for starting a record and stop it, start playback and stop it
|
||||
- Mouse Movement, click, and keyboard recording.
|
||||
- Smooth recording of the mouse.
|
||||
|
||||
# How does this work?
|
||||
|
||||
⚠️ **IMPORTANT** YOU NEED PYTHON TO RUN THIS SOFTWARE ⚠️
|
||||
\
|
||||
\
|
||||
To start recording, you simply have to press the red button or the `o` key (By default).\
|
||||
From there, you can move your mouse, click, and type on your keyboard, and everything will be recorded. (You can choose what will be recorded.)
|
||||
\
|
||||
\
|
||||
Then, to stop the recording, you simply click on the black square or press the `escape` key (By default).\
|
||||
To play a recording, you just need to click on the green play icon or press the `p` key (By default).
|
||||
And to stop the playback, press the `f3` key (By default).
|
||||
\
|
||||
\
|
||||
Here are some videos to show you the process:
|
||||
|
||||
|
||||
|
||||
|
||||
https://github.com/LOUDO56/PyMacroRecord/assets/117168736/3150d831-9d2e-4481-b72f-45dd5835ae26
|
||||
|
||||
|
||||
|
||||
https://github.com/LOUDO56/PyMacroRecord/assets/117168736/624e49b4-439e-413c-a054-a9586564a39e
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# For bug reports or update requests
|
||||
If you encounter a bug or want to request an update, simply create an issue [here](https://github.com/LOUDO56/PyMacroRecord/issues)
|
||||
|
||||
# My computer detects this program as a Virus
|
||||
|
||||
This is normal, my program is completely unknown to Windows, so at first, it may be considered as a virus. However, this is a false positive, so don't worry.\
|
||||
You can still check the code, this is an open-source program.
|
||||
\
|
||||
\
|
||||
But if you're still unsure, you can follow these steps to avoid the setup.exe:
|
||||
|
||||
- Download the source code like this:
|
||||
|
||||
![image](https://github.com/LOUDO56/PyMacroRecord/assets/117168736/ed511c68-da60-4cb9-b3be-25c0010e5b42)
|
||||
|
||||
|
||||
- Extract it wherever you want.
|
||||
- Go into the file and type `cmd` here, then press enter:
|
||||
|
||||
![image](https://github.com/LOUDO56/PyMacroRecord/assets/117168736/59bfd20b-0b86-4efc-86cd-dd4dec856c17)
|
||||
|
||||
- Type the command:
|
||||
```bash
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
- If you want these package to be on virtual environment follow these step [here](https://stackoverflow.com/a/41799834)
|
||||
- Finally, do `cd src` and type: `python software.py`
|
||||
- And boom! The software is now ready to use.
|
||||
|
||||
# License
|
||||
|
||||
This program is under [GNU General Public License v3.0](https://github.com/LOUDO56/PyMacroRecord/blob/main/LICENSE.md)
|
||||
|
||||
# Special Thanks
|
||||
|
||||
Special thanks to Fooinys, who playtested my program!
|
|
@ -1 +0,0 @@
|
|||
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"> <path d="M 7 4 C 6.744125 4 6.4879687 4.0974687 6.2929688 4.2929688 L 4.2929688 6.2929688 C 3.9019687 6.6839688 3.9019687 7.3170313 4.2929688 7.7070312 L 11.585938 15 L 4.2929688 22.292969 C 3.9019687 22.683969 3.9019687 23.317031 4.2929688 23.707031 L 6.2929688 25.707031 C 6.6839688 26.098031 7.3170313 26.098031 7.7070312 25.707031 L 15 18.414062 L 22.292969 25.707031 C 22.682969 26.098031 23.317031 26.098031 23.707031 25.707031 L 25.707031 23.707031 C 26.098031 23.316031 26.098031 22.682969 25.707031 22.292969 L 18.414062 15 L 25.707031 7.7070312 C 26.098031 7.3170312 26.098031 6.6829688 25.707031 6.2929688 L 23.707031 4.2929688 C 23.316031 3.9019687 22.682969 3.9019687 22.292969 4.2929688 L 15 11.585938 L 7.7070312 4.2929688 C 7.5115312 4.0974687 7.255875 4 7 4 z"/></svg>
|
After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 9.4 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 31 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px" height="24px"><path d="M 2 5 L 2 7 L 22 7 L 22 5 L 2 5 z M 2 11 L 2 13 L 22 13 L 22 11 L 2 11 z M 2 17 L 2 19 L 22 19 L 22 17 L 2 17 z"/></svg>
|
After Width: | Height: | Size: 217 B |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,2 @@
|
|||
nick;
|
||||
Domciencia
|
|
@ -0,0 +1,82 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PyMacroRecord | Download</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="script.js" defer></script>
|
||||
<meta name="description" content="Download page for PyMacroRecord.">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="icon" href="assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="assets/favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700;800&family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<div class="title">
|
||||
<img src="assets/logo.png" alt="pymacrorecord logo">
|
||||
<a href="/" class="title-software">PyMacroRecord</a>
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="download">Download</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord">Github</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord/blob/main/TUTORIAL.md">Help</a>
|
||||
<script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support Me on Ko-fi', '#6786F2', 'M4M0XL2LD');kofiwidget2.draw();</script>
|
||||
</div>
|
||||
<img src="assets/hamburger-icon.svg" alt="hambuger menu icon" class="hambuger-icon">
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="links-mobile-menu">
|
||||
<img src="assets/close.svg" alt="close icon" class="close-icon">
|
||||
<div class="links-mobile">
|
||||
<a href="download">Download</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord">Github</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord/blob/main/TUTORIAL.md">Help</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<div class="information-download">
|
||||
<div class="download-software">
|
||||
<p class="download-title">Download PyMacroRecord</p>
|
||||
<a class="download download-page" href="#">Download Setup for Windows</a>
|
||||
<p class="download-portable">Or... you can download the <a href="#" class="download-portable-link">portable</a> version</p>
|
||||
</div>
|
||||
|
||||
<div class="problem">
|
||||
<p class="download-title">Problems you might encounter by opening the setup</p>
|
||||
<p class="download-desc">
|
||||
You may encounter the Windows SmartScreen. This is normal, my program is unknown from Windows.
|
||||
Just click on “More Info” and “Run Anyway”
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="installation-not-windows">
|
||||
<p class="download-title">Installation for Linux and MacOS </p>
|
||||
<p class="download-desc">
|
||||
<p>1. If you didn't already, install <a href="https://www.python.org/downloads/" target="_blank">Python</a></p>
|
||||
<p>2. Download the last source code <a href="#" class="linked source-code">here</a>.</p>
|
||||
<p>3. Extract it wherever you want</p>
|
||||
<p>4. Open the terminal and type <code>cd <PATH TO SOFTWARE FOLDER></code></p>
|
||||
<p>5. Type the command:</p>
|
||||
<div><code class="pip-command">pip3 install -r requirements.txt</code></div>
|
||||
<p class="more-info">➡️ If you are on Linux, you might need to install Tkinter manually, commands to install are <a href="https://www.geeksforgeeks.org/how-to-install-tkinter-on-linux/" target="_blank">here</a></p>
|
||||
<p class="more-info">➡️ You need to remove the <b>win10toast</b> from <code>requirements.txt</code> or else you won't be able to install the depedencies</p>
|
||||
<p class="more-info">➡️ Mac Users, you must add terminal to accessibility and input monitoring settings in system preferences to allow mouse and keyboard inputs.</p>
|
||||
<p class="more-info">➡️ (Optional) If you want these package to be on virtual environment (depedencies of the software only in the folder) follow these step <a href="https://stackoverflow.com/a/41799834" target="_blank" class="linked">here</a>.</p>
|
||||
<p>6. Finally, type <code>cd src</code> and type the command: <code>python3 main.py</code></p>
|
||||
<p class="ready">And boom! The software is now ready to use.</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p class="credits">Created by LOUDO. Copyright © LOUDO <span class="current-year"></span>.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,249 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PyMacroRecord | Free And User Friendly Macro Recorder</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="script.js" defer></script>
|
||||
<meta name="description"
|
||||
content="
|
||||
PyMacroRecord is a Free Macro Recorder program that requires no scripting and is easy to use. You can record mouse movements, clicks and keyboard inputs. This Macro Recorder is free and
|
||||
has no premium features.
|
||||
">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="icon" href="assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="assets/favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700;800&family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<div class="title">
|
||||
<img src="assets/logo.png" alt="pymacrorecord logo">
|
||||
<a href="/" class="title-software">PyMacroRecord</a>
|
||||
</div>
|
||||
<div class="links">
|
||||
<a href="download">Download</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord">Github</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord/blob/main/TUTORIAL.md">Help</a>
|
||||
<script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support Me on Ko-fi', '#6786F2', 'M4M0XL2LD');kofiwidget2.draw();</script>
|
||||
</div>
|
||||
<img src="assets/hamburger-icon.svg" alt="hambuger menu icon" class="hambuger-icon">
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="links-mobile-menu">
|
||||
<img src="assets/close.svg" alt="close icon" class="close-icon">
|
||||
<div class="links-mobile">
|
||||
<a href="download">Download</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord">Github</a>
|
||||
<a href="https://github.com/LOUDO56/PyMacroRecord/blob/main/TUTORIAL.md">Help</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<div class="header-pres">
|
||||
<h1 class="big-title">PyMacroRecord</h1>
|
||||
<div>
|
||||
<img src="assets/logo.png" alt="pymacrorecord logo" class="logo-pres">
|
||||
</div>
|
||||
<h2 class="presentation">A Completely <span class="underline">Free</span> Macro Recorder Software.</h2>
|
||||
<h3 class="tired-of">Tired of repetitive tasks? Tired of learning difficult macro software, and tired of those "premium" purchases? PyMacroRecord is here for you!</h3>
|
||||
<a href="download" class="download">Download for Windows</a>
|
||||
<h4 class="not-windows">Not having Windows? Follow this <a href="download#installation-not-windows" class="linked">installation</a>!</h4>
|
||||
</div>
|
||||
|
||||
<div class="important-point">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 200"><path fill="#FFFfFF" fill-opacity="1" d="M0,96L80,85.3C160,75,320,53,480,69.3C640,85,800,139,960,149.3C1120,160,1280,128,1360,112L1440,96L1440,0L1360,0C1280,0,1120,0,960,0C800,0,640,0,480,0C320,0,160,0,80,0L0,0Z"></path></svg>
|
||||
<div class="box-pres">
|
||||
<img src="assets/no-premium.png" alt="no premium logo" class="no-premium">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Free, Forever
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
Unlike other Macro Recorder Software,
|
||||
PyMacroRecord is completely <span class="key-word">free</span> macro recorder to use.
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
No premium purchase required, all features unlocked.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres reversed">
|
||||
<img src="assets/github-logo.png" alt="github logo logo" class="github-logo">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Open Source
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
Despites being free, it is also an
|
||||
<span class="key-word">Open Source</span> Program!
|
||||
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
You can check the <a href="https://github.com/LOUDO56/PyMacroRecord" class="linked">source code</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="features-point">
|
||||
<p class="features-txt">Features</p>
|
||||
|
||||
<div class="box-pres">
|
||||
<img src="assets/checkmark.png" alt="cursor img" width="150">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Easy to use
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
PyMacroRecorder will make your life easier.
|
||||
You just have a red button to record and a play button to play your record.
|
||||
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
<img src="assets/preview.png" alt="preview of software" class="preview-software">
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres reversed">
|
||||
<img src="assets/cursor.png" alt="cursor img" width="150">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Fluid Record
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
PyMacroRecorder record your mouse
|
||||
and your Keyboard as well.
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
All this in a <span class="key-word">fluid</span> way.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres">
|
||||
<img src="assets/download.png" alt="download logo" width="170">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Save, Load and Share
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
With PyMacroRecorder, you can save, load and share with everyone your record.
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
It works with json, so the data is <span class="key-word">universal</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres reversed">
|
||||
<img src="assets/loop.png" alt="loop img" width="200">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Repetitions
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
You can choose as many repetitions as you like, without any <span class="key-word">restrictions</span>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres">
|
||||
<img src="assets/speed.png" alt="speed logo" width="170">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Speed
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
You can <span class="key-word">change</span> the speed of
|
||||
your records.
|
||||
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
From slow to fast.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres reversed">
|
||||
<img src="assets/interval.png" alt="interval logo" width="170">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
Interval
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
You can play your playbacks in <span class="key-word">interval</span>
|
||||
|
||||
</p>
|
||||
<p class="box-desc">
|
||||
Every x time, from 1 second to 1 day.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres">
|
||||
<img src="assets/for_loop.png" alt="for loop logo" width="150">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
For loop
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
You can play your playbacks <span class="key-word">for x time</span>
|
||||
</p>
|
||||
<p>
|
||||
From 1 second to 24 hours <span class="key-word">straight</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box-pres reversed">
|
||||
<img src="assets/laptop.png" alt="laptop logo" width="170">
|
||||
<div class="box-text">
|
||||
<p class="box-title">
|
||||
After Playback Actions
|
||||
</p>
|
||||
<p class="box-desc spaced">
|
||||
Once the recording is complete, you can add a after playback event, such as shutting down the computer, fully <span class="key-word">automatically</span>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="about-me">
|
||||
<p class="about-me-txt">About me</p>
|
||||
<p class="about-me-desc">
|
||||
Hello, I'm LOUDO, a passionate 18 years old French developer who loves coding.
|
||||
I'm currently studying web development to be a web developer.
|
||||
</p>
|
||||
<p class="about-me-desc">You can have a look of my <a href="https://github.com/LOUDO56" class="linked">Github Profile</a>.</p>
|
||||
<p class="about-me-desc">
|
||||
I wanted to create this software because I see a lot of macro recorders that have a lot of restrictions
|
||||
like repeat limit and to unlock them you have to pay a lot of money monthly or buy a license and some of them are also kinda hard to learn to be honest.
|
||||
So I hope this software will do the job!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="support">
|
||||
<p class="support-txt">Support <3</p>
|
||||
<p class="support-desc">Developing a software is not an easy task. If you really like this project, please consider making a small donation, it really helps and means a lot! <3</p>
|
||||
<p class="support-desc">By making a donation, your name will appear in the "Donors" section of PyMacroRecord as a thank you!</p>
|
||||
<p class="support-desc">Oh and thank you to the <span class="nb-top-donators"></span> <b><span class="last-donators"></span></b></p>
|
||||
<script type='text/javascript' src='https://storage.ko-fi.com/cdn/widget/Widget_2.js'></script><script type='text/javascript'>kofiwidget2.init('Support Me on Ko-fi', '#6786F2', 'M4M0XL2LD');kofiwidget2.draw();</script>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p class="credits">Created by LOUDO. Copyright © LOUDO <span class="current-year"></span>.</p>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +0,0 @@
|
|||
pynput==1.7.6
|
||||
pystray==0.19.4
|
||||
Pillow==10.0.0
|
||||
requests==2.31.0
|
||||
win10toast==0.9
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://www.pymacrorecord.com/sitemap.xml
|
|
@ -0,0 +1,52 @@
|
|||
const hamburger_icon = document.querySelector('.hambuger-icon');
|
||||
const close_menu_icon = document.querySelector('.close-icon');
|
||||
const mobile_menu = document.querySelector('.links-mobile-menu')
|
||||
|
||||
hamburger_icon.addEventListener('click', () => {
|
||||
mobile_menu.style.right = "0";
|
||||
})
|
||||
|
||||
close_menu_icon.addEventListener('click', () => {
|
||||
mobile_menu.style.right = "-300px";
|
||||
})
|
||||
|
||||
document.querySelector(".current-year").textContent = new Date().getFullYear();
|
||||
|
||||
|
||||
// To get the right version of the software without updating everytime the website
|
||||
if(window.location.href.includes('download')){
|
||||
const downdloadLink = document.querySelector('.download')
|
||||
const downloadPortable = document.querySelector('.download-portable-link');
|
||||
const sourceLink = document.querySelector('.source-code')
|
||||
fetch('https://api.github.com/repos/LOUDO56/PyMacroRecord/releases/latest')
|
||||
.then(resp => resp.json())
|
||||
.then(ver => {
|
||||
const setupToDl = 'https://github.com/LOUDO56/PyMacroRecord/releases/download/'+ver.tag_name+'/PyMacroRecord_'+ver.tag_name.replace('v', '')+'_Setup.exe'
|
||||
const portableToDl = 'https://github.com/LOUDO56/PyMacroRecord/releases/download/'+ver.tag_name+'/PyMacroRecord_'+ver.tag_name.replace('v', '')+'-portable.exe'
|
||||
const sourcetoDl = 'https://github.com/LOUDO56/PyMacroRecord/archive/refs/tags/'+ver.tag_name+'.zip'
|
||||
downdloadLink.href = setupToDl;
|
||||
downloadPortable.href = portableToDl
|
||||
sourceLink.href = sourcetoDl;
|
||||
})
|
||||
}
|
||||
|
||||
fetch("/donors.txt")
|
||||
.then(res => res.text())
|
||||
.then(data => {
|
||||
let lastDonators = "";
|
||||
maxDonators = 5
|
||||
data = data.split(";")
|
||||
if(data.length < 5){
|
||||
maxDonators = data.length
|
||||
}
|
||||
for(let i = 0; i < maxDonators; i++){
|
||||
if(i == maxDonators - 1 && i != 0) lastDonators += " and " + data[data.length - 1 - i]
|
||||
else lastDonators += data[data.length - 1 - i]
|
||||
if(i < maxDonators - 1 && maxDonators != 1 && i < maxDonators - 2){
|
||||
lastDonators += ","
|
||||
}
|
||||
}
|
||||
document.querySelector(".last-donators").textContent = lastDonators;
|
||||
if(maxDonators == 1) document.querySelector(".nb-top-donators").textContent = "last donator: ";
|
||||
else document.querySelector(".nb-top-donators").textContent = maxDonators += " last donators: ";
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset
|
||||
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
|
||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
|
||||
<url>
|
||||
<loc>https://www.pymacrorecord.com/</loc>
|
||||
<lastmod>2024-01-21T13:16:34+00:00</lastmod>
|
||||
<priority>1.00</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://www.pymacrorecord.com/download</loc>
|
||||
<lastmod>2024-01-21T13:16:34+00:00</lastmod>
|
||||
<priority>0.80</priority>
|
||||
</url>
|
||||
|
||||
</urlset>
|
Before Width: | Height: | Size: 847 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 466 B |
Before Width: | Height: | Size: 26 KiB |
|
@ -1,56 +0,0 @@
|
|||
from time import sleep
|
||||
from json import load, dumps, decoder
|
||||
from os import path, getenv
|
||||
|
||||
appdata_local = getenv('LOCALAPPDATA') + "/PyMacroRecord"
|
||||
appdata_local = appdata_local.replace('\\', "/")
|
||||
userSettingsPath = appdata_local + "/userSettings.json"
|
||||
|
||||
def getKeyPressed(keyboardListener, key):
|
||||
"""Return right key. canonical() prevents from weird characters to show up with ctrl active. Like ctrl + d,
|
||||
pynput will not print Key.ctrl and d, it will print Key.ctrl and a weird character"""
|
||||
if "Key." in str(key):
|
||||
keyPressed = str(key)
|
||||
else:
|
||||
keyPressed = str(keyboardListener.canonical(key)).replace("'", "")
|
||||
return keyPressed
|
||||
|
||||
|
||||
def simulateKeyPress(keyArray, special_keys, keyboardControl):
|
||||
"""Simulate keypress"""
|
||||
for keys in keyArray:
|
||||
keyToPress = keys if 'Key.' not in keys else special_keys[keys]
|
||||
keyboardControl.press(keyToPress)
|
||||
for keys in keyArray:
|
||||
keyToPress = keys if 'Key.' not in keys else special_keys[keys]
|
||||
keyboardControl.release(keyToPress)
|
||||
|
||||
|
||||
|
||||
def changeSettings(category, option=None, option2=None, newValue=None):
|
||||
"""Change settings of user"""
|
||||
userSettings = load(open(path.join(userSettingsPath)))
|
||||
if not category in userSettings:
|
||||
userSettings[category] = ""
|
||||
if newValue is None:
|
||||
if option is None:
|
||||
userSettings[category] = not userSettings[category]
|
||||
elif option2 is not None:
|
||||
userSettings[category][option][option2] = not userSettings[category][option][option2]
|
||||
else:
|
||||
userSettings[category][option] = not userSettings[category][option]
|
||||
|
||||
elif option is not None and newValue is not None:
|
||||
if option2 is not None:
|
||||
userSettings[category][option][option2] = newValue
|
||||
else:
|
||||
userSettings[category][option] = newValue
|
||||
userSettings_json = dumps(userSettings, indent=4)
|
||||
open(path.join(userSettingsPath), "w").write(userSettings_json)
|
||||
|
||||
def loadRecord():
|
||||
try:
|
||||
return load(open(path.join(userSettingsPath)))
|
||||
except decoder.JSONDecodeError as e:
|
||||
sleep(0.2)
|
||||
return load(open(path.join(userSettingsPath)))
|
227
src/macro.py
|
@ -1,227 +0,0 @@
|
|||
from threading import Thread
|
||||
from pynput import mouse, keyboard
|
||||
from pynput.mouse import Button
|
||||
from pynput.keyboard import Key
|
||||
from json import load, dumps, decoder
|
||||
from os import getenv, path
|
||||
from time import sleep, time
|
||||
|
||||
from global_function import *
|
||||
|
||||
appdata_local = getenv('LOCALAPPDATA') + "/PyMacroRecord" # Path where I store data
|
||||
appdata_local = appdata_local.replace('\\', "/")
|
||||
userSettingsPath = appdata_local + "/userSettings.json"
|
||||
macroEvents = {"events": []} # The core of this script, it serves to store all data events, so it can be replayable or saved on a file
|
||||
userSettings = loadRecord()
|
||||
|
||||
hotkeysDetection = []
|
||||
|
||||
mouseControl = mouse.Controller()
|
||||
keyboardControl = keyboard.Controller()
|
||||
special_keys = {
|
||||
"Key.esc": Key.esc, "Key.shift": Key.shift, "Key.tab": Key.tab, "Key.caps_lock": Key.caps_lock,
|
||||
"Key.ctrl": Key.ctrl, "Key.ctrl_l": Key.ctrl_l, "Key.ctrl_r": Key.ctrl_r, "Key.alt": Key.alt,
|
||||
"Key.alt_l": Key.alt_l, "Key.alt_r": Key.alt_r, "Key.cmd": Key.cmd, "Key.cmd_l": Key.cmd_l,
|
||||
"Key.cmd_r": Key.cmd_r, "Key.enter": Key.enter, "Key.backspace": Key.backspace, "Key.f19": Key.f19,
|
||||
"Key.f18": Key.f18, "Key.f17": Key.f17, "Key.f16": Key.f16, "Key.f15": Key.f15, "Key.f14": Key.f14,
|
||||
"Key.f13": Key.f13, "Key.media_volume_up": Key.media_volume_up, "Key.media_volume_down": Key.media_volume_down,
|
||||
"Key.media_volume_mute": Key.media_volume_mute, "Key.media_play_pause": Key.media_play_pause,
|
||||
"Key.f6": Key.f6, "Key.f5": Key.f5, "Key.f4": Key.f4, "Key.f3": Key.f3, "Key.f2": Key.f2, "Key.f1": Key.f1,
|
||||
"Key.insert": Key.insert, "Key.right": Key.right, "Key.down": Key.down, "Key.left": Key.left,
|
||||
"Key.up": Key.up, "Key.page_up": Key.page_up, "Key.page_down": Key.page_down, "Key.home": Key.home,
|
||||
"Key.end": Key.end, "Key.delete": Key.delete, "Key.space": Key.space,
|
||||
"Key.alt_gr": Key.alt_gr, "Key.menu": Key.menu, "Key.num_lock": Key.num_lock,
|
||||
"Key.pause": Key.pause, "Key.print_screen": Key.print_screen, "Key.scroll_lock": Key.scroll_lock,
|
||||
"Key.shift_l": Key.shift_l, "Key.shift_r": Key.shift_r,
|
||||
}
|
||||
|
||||
# Special keys are for on press and on release event so when the playback is on, it can press special keys without errors
|
||||
|
||||
record = False # Know if record is active
|
||||
playback = False # Know if playback is active
|
||||
|
||||
def win32_event_filter(msg, data):
|
||||
"""Detect if key is pressed by real keyboard or pynput"""
|
||||
global playback
|
||||
if data.flags & 0x10:
|
||||
if playback == True and record == False:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
# All events from mouse and keyboard when record is active
|
||||
def on_move(x, y):
|
||||
global start_time
|
||||
macroEvents["events"].append({'type': 'cursorMove', 'x': x, 'y': y, 'timestamp': time() - start_time})
|
||||
start_time = time()
|
||||
|
||||
|
||||
def on_click(x, y, button, pressed):
|
||||
global start_time
|
||||
if button == Button.left:
|
||||
macroEvents["events"].append(
|
||||
{'type': 'leftClickEvent', 'x': x, 'y': y, 'timestamp': time() - start_time, 'pressed': pressed})
|
||||
elif button == Button.right:
|
||||
macroEvents["events"].append(
|
||||
{'type': 'rightClickEvent', 'x': x, 'y': y, 'timestamp': time() - start_time, 'pressed': pressed})
|
||||
elif button == Button.middle:
|
||||
macroEvents["events"].append(
|
||||
{'type': 'middleClickEvent', 'x': x, 'y': y, 'timestamp': time() - start_time, 'pressed': pressed})
|
||||
start_time = time()
|
||||
|
||||
|
||||
def on_scroll(x, y, dx, dy):
|
||||
global start_time
|
||||
macroEvents["events"].append({'type': 'scrollEvent', 'dx': dx, 'dy': dy, 'timestamp': time() - start_time})
|
||||
start_time = time()
|
||||
|
||||
|
||||
def on_press(key):
|
||||
global start_time, playback, keyboard_listener, hotkeysDetection
|
||||
try:
|
||||
userSettings = loadRecord()
|
||||
except decoder.JSONDecodeError:
|
||||
pass
|
||||
if userSettings["Cant_rec"]:
|
||||
return
|
||||
keyPressed = getKeyPressed(keyboard_listener, key)
|
||||
if keyPressed not in hotkeysDetection:
|
||||
hotkeysDetection.append(keyPressed)
|
||||
if record == False and playback == False:
|
||||
#Start Record
|
||||
if hotkeysDetection == userSettings["Hotkeys"]["Record_Start"]:
|
||||
hotkeysDetection = []
|
||||
startRecord()
|
||||
|
||||
# Play Back
|
||||
if record == False and playback == False and path.exists(path.join(appdata_local + "/temprecord.json")):
|
||||
if hotkeysDetection == userSettings["Hotkeys"]["Playback_Start"]:
|
||||
hotkeysDetection = []
|
||||
Thread(target=playRec).start() # Thread to prevent hotkey not working
|
||||
|
||||
if record == False and playback == True:
|
||||
# Stop Playback
|
||||
if hotkeysDetection == userSettings["Hotkeys"]["Playback_Stop"]:
|
||||
hotkeysDetection = []
|
||||
playback = False
|
||||
|
||||
if record == True and playback == False:
|
||||
# Stop Record
|
||||
if hotkeysDetection == userSettings["Hotkeys"]["Record_Stop"]:
|
||||
hotkeysDetection = []
|
||||
stopRecord()
|
||||
if userSettings["Recordings"]["Keyboard"]:
|
||||
macroEvents["events"].append(
|
||||
{'type': 'keyboardEvent', 'key': keyPressed, 'timestamp': time() - start_time, 'pressed': True})
|
||||
start_time = time()
|
||||
|
||||
def on_release(key):
|
||||
global start_time
|
||||
if len(hotkeysDetection) != 0:
|
||||
hotkeysDetection.pop()
|
||||
keyPressed = getKeyPressed(keyboard_listener, key)
|
||||
if record == True and playback == False:
|
||||
if userSettings["Recordings"]["Keyboard"]:
|
||||
macroEvents["events"].append(
|
||||
{'type': 'keyboardEvent', 'key': keyPressed, 'timestamp': time() - start_time, 'pressed': False})
|
||||
start_time = time()
|
||||
|
||||
|
||||
def startRecord():
|
||||
"""
|
||||
Start record
|
||||
"""
|
||||
global start_time, mouse_listener, keyboard_listener, macroEvents, record, recordLenght, userSettings
|
||||
userSettings = loadRecord()
|
||||
record = True
|
||||
macroEvents = {'events': []}
|
||||
start_time = time()
|
||||
if userSettings["Recordings"]["Mouse_Move"] and userSettings["Recordings"]["Mouse_Click"]:
|
||||
mouse_listener = mouse.Listener(on_move=on_move, on_click=on_click, on_scroll=on_scroll)
|
||||
elif userSettings["Recordings"]["Mouse_Move"] and not userSettings["Recordings"]["Mouse_Click"]:
|
||||
mouse_listener = mouse.Listener(on_move=on_move, on_scroll=on_scroll)
|
||||
else:
|
||||
mouse_listener = mouse.Listener(on_click=on_click, on_scroll=on_scroll)
|
||||
print("record started")
|
||||
mouse_listener.start()
|
||||
|
||||
|
||||
def stopRecord():
|
||||
"""
|
||||
Stop record
|
||||
"""
|
||||
global macroEvents, record
|
||||
record = False
|
||||
mouse_listener.stop()
|
||||
try:
|
||||
macroEvents["events"].remove(macroEvents["events"][0]) # Remove hotkey start record of user
|
||||
macroEvents["events"].remove(macroEvents["events"][-1]) # Remove hotkey stop record of user
|
||||
except Exception:
|
||||
pass
|
||||
json_macroEvents = dumps(macroEvents, indent=4)
|
||||
print("record stopped")
|
||||
open(path.join(appdata_local + "/temprecord.json"), "w").write(json_macroEvents)
|
||||
|
||||
|
||||
def playRec():
|
||||
"""
|
||||
Playback function
|
||||
I retrieve data from temprecord to prevents conflict, like the user loaded a new record.
|
||||
Then I loop all the events, and for each event, he sleeps some time and then trigger is specific events.
|
||||
|
||||
To detect the stop of playback, I don't use the detection on the While loop because it won't work,
|
||||
and if I put the for loop in a thread, the playback is incredibly slow.
|
||||
"""
|
||||
global playback, keyboard_listener, hotkeysDetection
|
||||
userSettings = loadRecord()
|
||||
playback = True
|
||||
macroEvents = load(open(path.join(appdata_local + "/temprecord.json"), "r"))
|
||||
click_func = {
|
||||
"leftClickEvent": Button.left,
|
||||
"rightClickEvent": Button.right,
|
||||
"middleClickEvent": Button.middle,
|
||||
}
|
||||
print("playback started")
|
||||
changeSettings("NotDetectingKeyPressPlayBack")
|
||||
for repeat in range(userSettings["Playback"]["Repeat"]["Times"]):
|
||||
for events in range(len(macroEvents["events"])):
|
||||
if playback == False:
|
||||
changeSettings("StoppedRecManually")
|
||||
print("playback stopped manually")
|
||||
return
|
||||
sleep(macroEvents["events"][events]["timestamp"] * (1 / userSettings["Playback"]["Speed"]))
|
||||
event_type = macroEvents["events"][events]["type"]
|
||||
|
||||
if event_type == "cursorMove": # Cursor Move
|
||||
mouseControl.position = (macroEvents["events"][events]["x"], macroEvents["events"][events]["y"])
|
||||
|
||||
elif event_type in click_func: # Mouse Click
|
||||
mouseControl.position = (macroEvents["events"][events]["x"], macroEvents["events"][events]["y"])
|
||||
if macroEvents["events"][events]["pressed"] == True:
|
||||
mouseControl.press(click_func[event_type])
|
||||
else:
|
||||
mouseControl.release(click_func[event_type])
|
||||
|
||||
elif event_type == "scrollEvent":
|
||||
mouseControl.scroll(macroEvents["events"][events]["dx"], macroEvents["events"][events]["dy"])
|
||||
|
||||
elif event_type == "keyboardEvent": # Keyboard Press,Release
|
||||
if macroEvents["events"][events]["key"] != None:
|
||||
keyToPress = macroEvents["events"][events]["key"] if 'Key.' not in macroEvents["events"][events]["key"] else \
|
||||
special_keys[macroEvents["events"][events]["key"]]
|
||||
if playback == True:
|
||||
if macroEvents["events"][events]["pressed"] == True:
|
||||
keyboardControl.press(keyToPress)
|
||||
else:
|
||||
keyboardControl.release(keyToPress)
|
||||
|
||||
print("playback stopped")
|
||||
changeSettings("NotDetectingKeyPressPlayBack")
|
||||
hotkeysDetection = []
|
||||
simulateKeyPress(userSettings["Hotkeys"]["Playback_Stop"], special_keys, keyboardControl)
|
||||
playback = False
|
||||
|
||||
|
||||
|
||||
with keyboard.Listener(on_press=on_press, on_release=on_release, win32_event_filter=win32_event_filter) as keyboard_listener:
|
||||
keyboard_listener.join()
|
892
src/software.py
|
@ -1,892 +0,0 @@
|
|||
import threading
|
||||
import time
|
||||
from threading import Thread
|
||||
from json import load, dumps
|
||||
from tkinter import *
|
||||
from tkinter.ttk import *
|
||||
from tkinter import filedialog
|
||||
from tkinter import messagebox
|
||||
from pynput import keyboard
|
||||
from pynput.keyboard import Key
|
||||
from subprocess import Popen
|
||||
from os import path, mkdir, getenv, remove
|
||||
from webbrowser import open as OpenUrl
|
||||
from atexit import register
|
||||
import pystray
|
||||
from PIL import Image
|
||||
import os
|
||||
from sys import platform
|
||||
from requests import get as getVer
|
||||
|
||||
from global_function import *
|
||||
|
||||
version = "1.0.3"
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# SETUP PART
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
|
||||
special_keys = {
|
||||
"Key.esc": Key.esc, "Key.shift": Key.shift, "Key.tab": Key.tab, "Key.caps_lock": Key.caps_lock,
|
||||
"Key.ctrl": Key.ctrl, "Key.ctrl_l": Key.ctrl_l, "Key.ctrl_r": Key.ctrl_r, "Key.alt": Key.alt,
|
||||
"Key.alt_l": Key.alt_l, "Key.alt_r": Key.alt_r, "Key.cmd": Key.cmd, "Key.cmd_l": Key.cmd_l,
|
||||
"Key.cmd_r": Key.cmd_r, "Key.enter": Key.enter, "Key.backspace": Key.backspace, "Key.f19": Key.f19,
|
||||
"Key.f18": Key.f18, "Key.f17": Key.f17, "Key.f16": Key.f16, "Key.f15": Key.f15, "Key.f14": Key.f14,
|
||||
"Key.f13": Key.f13, "Key.media_volume_up": Key.media_volume_up, "Key.media_volume_down": Key.media_volume_down,
|
||||
"Key.media_volume_mute": Key.media_volume_mute, "Key.media_play_pause": Key.media_play_pause,
|
||||
"Key.f6": Key.f6, "Key.f5": Key.f5, "Key.f4": Key.f4, "Key.f3": Key.f3, "Key.f2": Key.f2, "Key.f1": Key.f1,
|
||||
"Key.insert": Key.insert, "Key.right": Key.right, "Key.down": Key.down, "Key.left": Key.left,
|
||||
"Key.up": Key.up, "Key.page_up": Key.page_up, "Key.page_down": Key.page_down, "Key.home": Key.home,
|
||||
"Key.end": Key.end, "Key.delete": Key.delete, "Key.space": Key.space,
|
||||
"Key.alt_gr": Key.alt_gr, "Key.menu": Key.menu, "Key.num_lock": Key.num_lock,
|
||||
"Key.pause": Key.pause, "Key.print_screen": Key.print_screen, "Key.scroll_lock": Key.scroll_lock,
|
||||
"Key.shift_l": Key.shift_l, "Key.shift_r": Key.shift_r,
|
||||
}
|
||||
# Special keys are for on press and on release event so when the playback is on, it can press special keys without errors
|
||||
|
||||
# Variable Setup
|
||||
|
||||
playbackStatement = False # Know if playback is active
|
||||
recordStatement = False # Know if record is active
|
||||
recordSet = False # Know if user set recorded so he can save it
|
||||
fileAlreadySaved = False # Know if user already save is macro once so not neet to save as
|
||||
closeWindow = False # Know if user is about to close the window
|
||||
changeKey = False # Know to change hotkey
|
||||
cantRec = False # Prevents recording from settings speed and repeat setup
|
||||
macroPath = None
|
||||
hotkeyVisible = [] # For Gui visual
|
||||
hotkeysDetection = [] # Detect Hotkeys
|
||||
keyboardControl = keyboard.Controller() # Keyboard controller to detect keypress
|
||||
|
||||
appdata_local = getenv('LOCALAPPDATA') + "/PyMacroRecord"
|
||||
appdata_local = appdata_local.replace('\\', "/")
|
||||
userSettingsPath = appdata_local + "/userSettings.json"
|
||||
|
||||
# Prevents from playing last record not saved or saved when re-launching the software
|
||||
if path.exists(path.join(appdata_local + "/temprecord.json")):
|
||||
remove(path.join(appdata_local + "/temprecord.json"))
|
||||
|
||||
# Setup of UserSettings if not exists
|
||||
if path.isdir(appdata_local) == False:
|
||||
mkdir(appdata_local) # Temp record to interact with macro.py
|
||||
userSettings = {
|
||||
"Playback": {
|
||||
"Speed": 1,
|
||||
"Repeat": {
|
||||
"Mode": "OneTime",
|
||||
"Times": 1,
|
||||
"For": 0,
|
||||
"Interval": 0
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
"Recordings": {
|
||||
"Mouse_Move": True,
|
||||
"Mouse_Click": True,
|
||||
"Keyboard": True,
|
||||
},
|
||||
|
||||
"Hotkeys": {
|
||||
"Record_Start": [
|
||||
"o"
|
||||
],
|
||||
"Record_Stop": [
|
||||
"Key.esc"
|
||||
],
|
||||
"Playback_Start": [
|
||||
"p"
|
||||
],
|
||||
"Playback_Stop": [
|
||||
"Key.f3"
|
||||
],
|
||||
},
|
||||
|
||||
"Minimization": {
|
||||
"When_Playing": False,
|
||||
"When_Recording": False,
|
||||
},
|
||||
|
||||
"Run_On_StartUp": False,
|
||||
|
||||
"After_Playback": {
|
||||
"Mode": "Idle" # Quit, Lock Computer, Lof off computer, Turn off computer, Standby, Hibernate
|
||||
},
|
||||
|
||||
"Cant_rec": False,
|
||||
"StoppedRecManually": False,
|
||||
"NotDetectingKeyPressPlayBack": False
|
||||
|
||||
}
|
||||
|
||||
userSettings_json = dumps(userSettings, indent=4)
|
||||
open(path.join(userSettingsPath), "w").write(userSettings_json)
|
||||
|
||||
userSettings = loadRecord()
|
||||
|
||||
if "StoppedRecManually" not in userSettings or "NotDetectingKeyPressPlayBack" not in userSettings:
|
||||
userSettings["StoppedRecManually"] = False
|
||||
userSettings["NotDetectingKeyPressPlayBack"] = False
|
||||
userSettings_json = dumps(userSettings, indent=4)
|
||||
open(path.join(userSettingsPath), "w").write(userSettings_json)
|
||||
|
||||
if userSettings["StoppedRecManually"]:
|
||||
changeSettings("StoppedRecManually")
|
||||
|
||||
if userSettings["NotDetectingKeyPressPlayBack"]:
|
||||
changeSettings("NotDetectingKeyPressPlayBack")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# KEYBOARD INPUT DETECTION
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
def win32_event_filter(msg, data):
|
||||
"""Detect if key is pressed by real keyboard or pynput"""
|
||||
userSettings = loadRecord()
|
||||
if data.flags & 0x10:
|
||||
if playbackStatement == True and recordStatement == False:
|
||||
if userSettings["NotDetectingKeyPressPlayBack"]:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def on_press(key):
|
||||
"""
|
||||
Detect key release to change buttons in the gui
|
||||
"""
|
||||
global changeKey, recordStatement, playbackStatement, recordBtn, playBtn, recordSet, userSettings, hotkeysDetection, hotkeyVisible, cantRec, hotkey, changeKey
|
||||
userSettings = loadRecord()
|
||||
if changeKey == True: # To print hotkeys of users in settings
|
||||
keyPressed = getKeyPressed(keyboardListener, key)
|
||||
if keyPressed not in hotkey:
|
||||
hotkey.append(keyPressed)
|
||||
keyPressed = keyPressed.replace("Key.", "").replace("_l", "").replace("_r", "").replace("_gr", "")
|
||||
hotkeyVisible.append(keyPressed.upper())
|
||||
entryToChange.configure(text=hotkeyVisible)
|
||||
for hotkeyUser in userSettings["Hotkeys"]:
|
||||
if userSettings["Hotkeys"][hotkeyUser] == hotkey:
|
||||
messagebox.showerror("Error", "You cannot have the same keyboard shortcut for another category.")
|
||||
entryToChange.configure(text="Please key")
|
||||
hotkey = []
|
||||
hotkeyVisible = []
|
||||
else:
|
||||
if all(keyword not in keyPressed for keyword in ["ctrl", "alt", "shift"]):
|
||||
changeSettings("Hotkeys", typeOfHotKey, None, hotkey)
|
||||
changeKey = False
|
||||
hotkeyVisible = []
|
||||
|
||||
if changeKey == False and cantRec == False:
|
||||
keyPressed = getKeyPressed(keyboardListener, key)
|
||||
if keyPressed not in hotkeysDetection:
|
||||
hotkeysDetection.append(keyPressed)
|
||||
if hotkeysDetection == userSettings["Hotkeys"][
|
||||
"Record_Start"] and recordStatement == False and playbackStatement == False:
|
||||
startRecordingAndChangeImg(False)
|
||||
|
||||
if hotkeysDetection == userSettings["Hotkeys"][
|
||||
"Record_Stop"] and recordStatement == True and playbackStatement == False:
|
||||
stopRecordingAndChangeImg(False)
|
||||
|
||||
if hotkeysDetection == userSettings["Hotkeys"][
|
||||
"Playback_Start"] and recordStatement == False and playbackStatement == False and recordSet == True:
|
||||
startPlayback(False)
|
||||
|
||||
|
||||
elif hotkeysDetection == userSettings["Hotkeys"][
|
||||
"Playback_Stop"] and recordStatement == False and playbackStatement == True:
|
||||
stopPlayback()
|
||||
|
||||
|
||||
def on_release(key):
|
||||
global hotkeysDetection
|
||||
if len(hotkeysDetection) != 0:
|
||||
hotkeysDetection.pop()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# RECORD MANAGEMENTS
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
|
||||
def preventRecord(state):
|
||||
global cantRec
|
||||
if state:
|
||||
cantRec = True
|
||||
else:
|
||||
cantRec = False
|
||||
changeSettings("Cant_rec")
|
||||
|
||||
def startRecordingAndChangeImg(pressKey=True):
|
||||
"""
|
||||
When this function is called, it presses for the user his keybind to start the recording if he pressed the button
|
||||
instead of his keyboard, it changes the button image
|
||||
"""
|
||||
global stopBtn, recordStatement, playbackStatement, cantRec
|
||||
if pressKey:
|
||||
if recordSet == True and fileAlreadySaved == False:
|
||||
preventRecord(True)
|
||||
wantToSave = warningPopUpSave()
|
||||
if wantToSave:
|
||||
saveMacro()
|
||||
if macroPath == None:
|
||||
return
|
||||
elif wantToSave == False:
|
||||
pass
|
||||
elif wantToSave == None:
|
||||
preventRecord(False)
|
||||
return
|
||||
preventRecord(False)
|
||||
userSettings = loadRecord()
|
||||
playBtn.configure(state=DISABLED)
|
||||
if playbackStatement == False:
|
||||
recordStatement = True
|
||||
file_menu.entryconfig('Load', state=DISABLED)
|
||||
if pressKey:
|
||||
simulateKeyPress(userSettings["Hotkeys"]["Record_Start"], special_keys, keyboardControl)
|
||||
recordBtn.configure(image=stopImg, command=stopRecordingAndChangeImg)
|
||||
file_menu.entryconfig('Save', state=DISABLED)
|
||||
file_menu.entryconfig('Save as', state=DISABLED)
|
||||
file_menu.entryconfig('New', state=DISABLED)
|
||||
file_menu.entryconfig('Load', state=DISABLED)
|
||||
if userSettings["Minimization"]["When_Recording"]:
|
||||
window.withdraw()
|
||||
threading.Thread(target=showNotifWithdraw).start()
|
||||
|
||||
|
||||
def stopRecordingAndChangeImg(pressKey=True):
|
||||
"""
|
||||
When this function is called, it presses for the user his keybind to stop the recording if he pressed the button
|
||||
instead of his keyboard, it changes the button image
|
||||
"""
|
||||
global recordBtn, recordStatement, recordSet
|
||||
userSettings = loadRecord()
|
||||
recordStatement = False
|
||||
recordSet = True
|
||||
if pressKey:
|
||||
simulateKeyPress(userSettings["Hotkeys"]["Record_Stop"], special_keys, keyboardControl)
|
||||
recordBtn.configure(image=recordImg, command=startRecordingAndChangeImg)
|
||||
playBtn.configure(state=NORMAL)
|
||||
file_menu.entryconfig('Save', state=NORMAL, command=saveMacro)
|
||||
file_menu.entryconfig('Save as', state=NORMAL, command=saveMacroAs)
|
||||
file_menu.entryconfig('New', state=NORMAL, command=newMacro)
|
||||
file_menu.entryconfig('Load', state=NORMAL)
|
||||
if userSettings["Minimization"]["When_Recording"]:
|
||||
window.deiconify()
|
||||
|
||||
|
||||
def startPlayback(pressKey=True):
|
||||
"""
|
||||
Playback the last recorded macro or the loaded one
|
||||
"""
|
||||
global playbackStatement, recordBtn, recordSet
|
||||
userSettings = loadRecord()
|
||||
if userSettings["StoppedRecManually"]:
|
||||
changeSettings("StoppedRecManually")
|
||||
playBtn.configure(image=stopImg, command=stopPlayback)
|
||||
playbackStatement = True
|
||||
file_menu.entryconfig('Save', state=DISABLED)
|
||||
file_menu.entryconfig('Save as', state=DISABLED)
|
||||
file_menu.entryconfig('New', state=DISABLED)
|
||||
file_menu.entryconfig('Load', state=DISABLED)
|
||||
if pressKey:
|
||||
simulateKeyPress(userSettings["Hotkeys"]["Playback_Start"], special_keys, keyboardControl)
|
||||
recordBtn.configure(state=DISABLED)
|
||||
if userSettings["Minimization"]["When_Playing"]:
|
||||
window.withdraw()
|
||||
threading.Thread(target=showNotifWithdraw).start()
|
||||
|
||||
|
||||
def stopPlayback():
|
||||
global playbackStatement, userSettings
|
||||
simulateKeyPress(userSettings["Hotkeys"]["Playback_Stop"], special_keys, keyboardControl)
|
||||
playbackStatement = False
|
||||
recordBtn.configure(state=NORMAL)
|
||||
playBtn.configure(image=playImg, command=startPlayback)
|
||||
file_menu.entryconfig('New', state=NORMAL)
|
||||
file_menu.entryconfig('Load', state=NORMAL)
|
||||
file_menu.entryconfig('Save', state=NORMAL)
|
||||
file_menu.entryconfig('Save as', state=NORMAL)
|
||||
userSettings = loadRecord()
|
||||
if userSettings["NotDetectingKeyPressPlayBack"]:
|
||||
changeSettings("NotDetectingKeyPressPlayBack")
|
||||
if not userSettings["StoppedRecManually"]:
|
||||
if userSettings["After_Playback"]["Mode"] != "Idle":
|
||||
cleanup()
|
||||
window.quit()
|
||||
if userSettings["After_Playback"]["Mode"] == "Standy":
|
||||
os.system("rundll32.exe powrprof.dll, SetSuspendState Sleep")
|
||||
if userSettings["After_Playback"]["Mode"] == "Log off Computer":
|
||||
os.system("shutdown /l")
|
||||
if userSettings["After_Playback"]["Mode"] == "Turn off Computer":
|
||||
os.system("shutdown /s")
|
||||
if userSettings["After_Playback"]["Mode"] == "Hibernate (If activated)":
|
||||
os.system("shutdown -h")
|
||||
else:
|
||||
changeSettings("StoppedRecManually")
|
||||
if userSettings["Minimization"]["When_Playing"]:
|
||||
window.deiconify()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# RECORD FILE MANAGEMENTS
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
def saveMacroAs(e=None):
|
||||
"""
|
||||
Save the macro as a file name, I use temprecord.json to retrieve the last record and then save it on his file
|
||||
"""
|
||||
global macroPath, fileAlreadySaved, cantRec
|
||||
if recordStatement == False and playbackStatement == False and recordSet == True:
|
||||
if cantRec == False:
|
||||
preventRecord(True)
|
||||
macroSaved = filedialog.asksaveasfile(filetypes=[('Json Files', '*.json')], defaultextension='.json')
|
||||
if macroSaved is not None:
|
||||
macroContent = open(path.join(appdata_local + "/temprecord.json"), "r")
|
||||
macroEvents = load(macroContent)
|
||||
json_macroEvents = dumps(macroEvents, indent=4)
|
||||
open(macroSaved.name, "w").write(json_macroEvents)
|
||||
macroPath = macroSaved.name
|
||||
# macroPath serve to know where his last saved macro is, so the user can
|
||||
# overwrite it if he did a new record
|
||||
macroSaved.close()
|
||||
fileAlreadySaved = True
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def saveMacro(e=None):
|
||||
"""
|
||||
Save the last macro saved or loaded, I use temprecord.json to retrieve the last record and then save it on his file
|
||||
"""
|
||||
if recordStatement == False and playbackStatement == False and recordSet == True:
|
||||
if fileAlreadySaved == True:
|
||||
macroContent = open(path.join(appdata_local + "/temprecord.json"), "r")
|
||||
macroSaved = open(path.join(macroPath), "w")
|
||||
macroEvents = load(macroContent)
|
||||
json_macroEvents = dumps(macroEvents, indent=4)
|
||||
macroSaved.write(json_macroEvents)
|
||||
else:
|
||||
saveMacroAs()
|
||||
if closeWindow:
|
||||
window.destroy()
|
||||
|
||||
|
||||
def loadMacro(e=None):
|
||||
"""
|
||||
Load a script that the user did, to play it or overwrite it (if he saved his new record of course)
|
||||
"""
|
||||
global macroPath, recordSet, fileAlreadySaved, cantRec
|
||||
preventRecord(True)
|
||||
if recordStatement == False and playbackStatement == False:
|
||||
if recordSet == True:
|
||||
wantToSave = warningPopUpSave()
|
||||
if wantToSave == None:
|
||||
preventRecord(False)
|
||||
return
|
||||
elif wantToSave:
|
||||
saveMacro()
|
||||
macroFile = filedialog.askopenfile(filetypes=[('Json Files', '*.json')], defaultextension='.json')
|
||||
if macroFile is not None:
|
||||
macroContent = open(macroFile.name)
|
||||
macroPath = macroFile.name
|
||||
macroEvents = load(macroContent)
|
||||
json_macroEvents = dumps(macroEvents, indent=4)
|
||||
open(path.join(appdata_local + "/temprecord.json"), "w").write(json_macroEvents)
|
||||
playBtn.configure(state=NORMAL)
|
||||
file_menu.entryconfig('Save', state=NORMAL, command=saveMacro)
|
||||
file_menu.entryconfig('Save as', state=NORMAL, command=saveMacroAs)
|
||||
file_menu.entryconfig('New', state=NORMAL, command=newMacro)
|
||||
macroFile.close()
|
||||
fileAlreadySaved = True
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def newMacro(e=None):
|
||||
"""
|
||||
Load a script that the user did, to play it or overwrite it (if he saved his new record of course)
|
||||
"""
|
||||
global recordSet, fileAlreadySaved
|
||||
if recordStatement == False and playbackStatement == False:
|
||||
if recordSet == True:
|
||||
preventRecord(True)
|
||||
wantToSave = warningPopUpSave()
|
||||
if wantToSave == None:
|
||||
preventRecord(True)
|
||||
return
|
||||
elif wantToSave:
|
||||
saveMacro()
|
||||
recordBtn.configure(image=recordImg, command=startRecordingAndChangeImg)
|
||||
file_menu.entryconfig('Save', state=DISABLED)
|
||||
file_menu.entryconfig('Save as', state=DISABLED)
|
||||
file_menu.entryconfig('New', state=DISABLED)
|
||||
playBtn.configure(state=DISABLED)
|
||||
recordSet = False
|
||||
fileAlreadySaved = False
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# USER SETTINGS MANAGEMENTS
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
|
||||
def showNotifWithdraw():
|
||||
if platform == "win32":
|
||||
try:
|
||||
from win10toast import ToastNotifier
|
||||
|
||||
toast = ToastNotifier()
|
||||
toast.show_toast(
|
||||
title="PyMacroRecord minimized",
|
||||
msg="PyMacroRecord has been minimized",
|
||||
duration=5,
|
||||
icon_path="assets/logo.ico"
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
print("The win10toast module is not available. Unable to display toast notification on this system.")
|
||||
elif platform == "linux":
|
||||
os.system('notify-send -u normal "PyMacroRecord" "PyMacroRecord has been minimized"')
|
||||
elif platform == "darwin":
|
||||
os.system("""
|
||||
osascript -e 'display notification "PyMacroRecord" with title "PyMacroRecord has been minimized"
|
||||
""")
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
def warningPopUpSave():
|
||||
"""Just popup a window to say 'Do you want to save your record?'
|
||||
So the user don't lost his last record accidentally"""
|
||||
return messagebox.askyesnocancel("Info", "Do you want to save your record?")
|
||||
|
||||
|
||||
def stopProgram():
|
||||
global closeWindow, macro_process, cantRec
|
||||
"""
|
||||
When the users want to stop the software, the macro.py in the background is terminated
|
||||
And it checks if the users did a record but did not save it
|
||||
"""
|
||||
if recordSet == True and fileAlreadySaved == False:
|
||||
preventRecord(True)
|
||||
closeWindow = True
|
||||
wantToSave = warningPopUpSave()
|
||||
if wantToSave:
|
||||
saveMacro()
|
||||
elif wantToSave == False:
|
||||
macro_process.terminate()
|
||||
window.destroy()
|
||||
icon.stop()
|
||||
preventRecord(False)
|
||||
else:
|
||||
macro_process.terminate()
|
||||
window.destroy()
|
||||
icon.stop()
|
||||
|
||||
|
||||
def validate_input(action, value_if_allowed):
|
||||
"""Prevents from adding letters on an Entry label"""
|
||||
if action == "1": # Insert
|
||||
try:
|
||||
float(value_if_allowed)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def changeSpeed():
|
||||
"""Gui to change the speeds record"""
|
||||
global speedWin, horizontal_line, speedTestVal, cantRec
|
||||
preventRecord(True)
|
||||
speedWin = Toplevel(window)
|
||||
w = 300
|
||||
h = 150
|
||||
speedWin.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
speedWin.title("Change Speed")
|
||||
speedWin.grab_set()
|
||||
speedWin.resizable(False, False)
|
||||
speedWin.attributes("-toolwindow", 1)
|
||||
Label(speedWin, text="Enter Speed Number between 0.1 and 10", font=('Segoe UI', 10)).pack(side=TOP, pady=10)
|
||||
userSettings = loadRecord()
|
||||
setNewSpeedInput = Entry(speedWin, width=10)
|
||||
setNewSpeedInput.insert(0, str(userSettings["Playback"]["Speed"]))
|
||||
setNewSpeedInput.pack(pady=20)
|
||||
buttonArea = Frame(speedWin)
|
||||
Button(buttonArea, text="Confirm", command=lambda: setNewSpeedNumber(setNewSpeedInput.get())).pack(side=LEFT,
|
||||
padx=10)
|
||||
Button(buttonArea, text="Cancel", command=speedWin.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(speedWin)
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def setNewSpeedNumber(val):
|
||||
"""Function to set the new Speed numbers and to check if the value is good"""
|
||||
try:
|
||||
if float(val) <= 0 or float(val) > 10:
|
||||
messagebox.showerror("Wrong Speed Number", "Your speed value must be between 0.1 and 10!")
|
||||
else:
|
||||
changeSettings("Playback", "Speed", None, float(val))
|
||||
speedWin.destroy()
|
||||
except ValueError:
|
||||
messagebox.showerror("Wrong Speed Number", "Your input must be a number!")
|
||||
|
||||
|
||||
def repeatGuiSettings():
|
||||
"""Gui to set amount of repeat"""
|
||||
global cantRec
|
||||
preventRecord(True)
|
||||
repeatGui = Toplevel(window)
|
||||
w = 300
|
||||
h = 150
|
||||
repeatGui.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
repeatGui.title("Change Speed")
|
||||
repeatGui.grab_set()
|
||||
repeatGui.resizable(False, False)
|
||||
repeatGui.attributes("-toolwindow", 1)
|
||||
Label(repeatGui, text="Enter Repeat Number ", font=('Segoe UI', 10)).pack(side=TOP, pady=10)
|
||||
userSettings = loadRecord()
|
||||
repeatTimes = Spinbox(repeatGui, from_=1, to=100000000, width=7, validate="key",
|
||||
validatecommand=(validate_cmd, "%d", "%P"))
|
||||
repeatTimes.insert(0, userSettings["Playback"]["Repeat"]["Times"])
|
||||
repeatTimes.pack(pady=20)
|
||||
buttonArea = Frame(repeatGui)
|
||||
Button(buttonArea, text="Confirm",
|
||||
command=lambda: [changeSettings("Playback", "Repeat", "Times", int(repeatTimes.get())),
|
||||
repeatGui.destroy()]).pack(side=LEFT, padx=10)
|
||||
Button(buttonArea, text="Cancel", command=repeatGui.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(repeatGui)
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def afterPlaybackGui():
|
||||
"""Gui to set mode after playback, like shutting down computer, stuff like that"""
|
||||
global cantRec
|
||||
preventRecord(True)
|
||||
playBackGui = Toplevel(window)
|
||||
w = 250
|
||||
h = 150
|
||||
playBackGui.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
playBackGui.title("After Playback")
|
||||
playBackGui.grab_set()
|
||||
playBackGui.resizable(False, False)
|
||||
playBackGui.attributes("-toolwindow", 1)
|
||||
|
||||
options = [
|
||||
'Idle',
|
||||
'Quit Software',
|
||||
'Standy',
|
||||
'Log off Computer',
|
||||
'Turn off Computer',
|
||||
'Hibernate (If activated)'
|
||||
|
||||
]
|
||||
menuOptions = LabelFrame(playBackGui, text="On playback complete")
|
||||
AfterPlaybackOption = StringVar()
|
||||
userSettings = loadRecord()
|
||||
OptionMenu(menuOptions, AfterPlaybackOption, userSettings["After_Playback"]["Mode"], *options).pack(fill="both",
|
||||
padx=10,
|
||||
pady=10)
|
||||
menuOptions.pack(fill="both", padx=5, pady=10)
|
||||
buttonArea = Frame(playBackGui)
|
||||
Button(buttonArea, text="Confirm",
|
||||
command=lambda: [changeSettings("After_Playback", "Mode", None, AfterPlaybackOption.get()),
|
||||
playBackGui.destroy()]).pack(side=LEFT, padx=10)
|
||||
Button(buttonArea, text="Cancel", command=playBackGui.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(playBackGui)
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def hotkeySettingsGui():
|
||||
"""Gui to set up new Hotkeys"""
|
||||
global typeOfHotKey, startKey, stopKey, playbackStartKey, playbackStopKey, cantRec, changeKey
|
||||
userSettings = loadRecord()
|
||||
preventRecord(True)
|
||||
hotkeyGui = Toplevel(window)
|
||||
w = 300
|
||||
h = 200
|
||||
hotkeyGui.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
hotkeyGui.title("Hotkey settings")
|
||||
hotkeyGui.grab_set()
|
||||
hotkeyGui.resizable(False, False)
|
||||
hotkeyGui.attributes("-toolwindow", 1)
|
||||
hotkeyLine = Frame(hotkeyGui)
|
||||
hotkeyStart = userSettings["Hotkeys"]["Record_Start"]
|
||||
hotkeyStop = userSettings["Hotkeys"]["Record_Stop"]
|
||||
hotkeyPlaybackStart = userSettings["Hotkeys"]["Playback_Start"]
|
||||
hotkeyPlaybackStop = userSettings["Hotkeys"]["Playback_Stop"]
|
||||
hotkeyVisible = [hotkeyStart, hotkeyStop, hotkeyPlaybackStart, hotkeyPlaybackStop]
|
||||
cleanedHotkeys = []
|
||||
for key in hotkeyVisible:
|
||||
if isinstance(key, list):
|
||||
cleanedSublist = [
|
||||
subkey.replace("Key.", "").replace("_l", "").replace("_r", "").replace("_gr", "").upper() if isinstance(
|
||||
subkey, str) else subkey for subkey in key]
|
||||
cleanedHotkeys.append(cleanedSublist)
|
||||
else:
|
||||
cleanedKey = key.replace("Key.", "").replace("_l", "").replace("_r", "").replace("_gr", "").upper()
|
||||
cleanedHotkeys.append(cleanedKey)
|
||||
hotkeyVisible = cleanedHotkeys
|
||||
Button(hotkeyLine, text="Start Record", command=lambda: enableHotKeyDetection("Record_Start", startKey)).grid(row=0,
|
||||
column=0,
|
||||
padx=10)
|
||||
startKey = Label(hotkeyLine, text=hotkeyVisible[0], font=('Segoe UI', 12))
|
||||
startKey.grid(row=0, column=1, pady=5)
|
||||
|
||||
Button(hotkeyLine, text="Stop Record", command=lambda: enableHotKeyDetection("Record_Stop", stopKey)).grid(row=1,
|
||||
column=0,
|
||||
padx=10)
|
||||
stopKey = Label(hotkeyLine, text=hotkeyVisible[1], font=('Segoe UI', 12))
|
||||
stopKey.grid(row=1, column=1, pady=5)
|
||||
|
||||
Button(hotkeyLine, text="Playback Start",
|
||||
command=lambda: enableHotKeyDetection("Playback_Start", playbackStartKey)).grid(row=2, column=0, padx=10)
|
||||
playbackStartKey = Label(hotkeyLine, text=hotkeyVisible[2], font=('Segoe UI', 12))
|
||||
playbackStartKey.grid(row=2, column=1, pady=5)
|
||||
|
||||
Button(hotkeyLine, text="Playback Stop",
|
||||
command=lambda: enableHotKeyDetection("Playback_Stop", playbackStopKey)).grid(row=3, column=0, padx=10)
|
||||
playbackStopKey = Label(hotkeyLine, text=hotkeyVisible[3], font=('Segoe UI', 12))
|
||||
playbackStopKey.grid(row=3, column=1, pady=5)
|
||||
|
||||
hotkeyLine.pack()
|
||||
|
||||
buttonArea = Frame(hotkeyGui)
|
||||
Button(buttonArea, text="Close", command=hotkeyGui.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(hotkeyGui)
|
||||
changeKey = False
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def enableHotKeyDetection(mode, entry):
|
||||
"""Just enable Hotkeys detection to change them"""
|
||||
global changeKey, typeOfHotKey, hotkey, entryToChange
|
||||
changeKey = True
|
||||
typeOfHotKey = mode
|
||||
hotkey = []
|
||||
entry.configure(text="Please key")
|
||||
entryToChange = entry
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------ #
|
||||
#
|
||||
#
|
||||
# GUI (VISUAL) MANAGEMENTS
|
||||
#
|
||||
#
|
||||
# ------------------------------------------------------------------------------ #
|
||||
|
||||
|
||||
def aboutMeGui():
|
||||
global cantRec
|
||||
preventRecord(True)
|
||||
aboutGui = Toplevel(window)
|
||||
w = 300
|
||||
h = 200
|
||||
aboutGui.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
aboutGui.title("About")
|
||||
aboutGui.grab_set()
|
||||
aboutGui.resizable(False, False)
|
||||
aboutGui.attributes("-toolwindow", 1)
|
||||
Label(aboutGui, text="Publisher: LOUDO").pack(side=TOP, pady=3)
|
||||
Label(aboutGui, text=f"Version: {version} ({versionUpToDate})").pack(side=TOP, pady=3)
|
||||
Label(aboutGui, text="Under License: General Public License v3.0").pack(side=TOP, pady=3)
|
||||
Label(aboutGui, text="And... i like javascript, wbu?").pack(side=TOP, pady=15)
|
||||
buttonArea = Frame(aboutGui)
|
||||
Button(buttonArea, text="Close", command=aboutGui.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(aboutGui)
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def newVerAvailable(ver):
|
||||
global cantRec
|
||||
preventRecord(True)
|
||||
newVerGui = Toplevel(window)
|
||||
w = 300
|
||||
h = 130
|
||||
newVerGui.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
newVerGui.title("New Version Available!")
|
||||
newVerGui.grab_set()
|
||||
newVerGui.resizable(False, False)
|
||||
newVerGui.attributes("-toolwindow", 1)
|
||||
Label(newVerGui, text=f"New Version {ver} available!").pack(side=TOP)
|
||||
Label(newVerGui, text="Click the button to open releases page on GitHub").pack(side=TOP)
|
||||
buttonArea = Frame(newVerGui)
|
||||
Button(buttonArea, text="Click here to view",
|
||||
command=lambda: OpenUrl("https://github.com/LOUDO56/macro-recorder/releases")).pack(side=LEFT, pady=10)
|
||||
Button(buttonArea, text="Close", command=newVerGui.destroy).pack(side=LEFT, padx=10)
|
||||
buttonArea.pack(side=BOTTOM, pady=10)
|
||||
window.wait_window(newVerGui)
|
||||
preventRecord(False)
|
||||
|
||||
|
||||
def systemTray():
|
||||
"""Just to show little icon on system tray"""
|
||||
global icon
|
||||
image = Image.open("assets/logo.ico")
|
||||
menu = (
|
||||
pystray.MenuItem('Show', action=window.deiconify, default=True),
|
||||
)
|
||||
icon = pystray.Icon("name", image, "PyMacroRecord", menu)
|
||||
icon.run()
|
||||
|
||||
|
||||
def cleanup():
|
||||
"""
|
||||
When the users want to stop the software, the macro.py in the background is terminated
|
||||
"""
|
||||
if 'macro_process' in globals():
|
||||
macro_process.terminate()
|
||||
icon.stop()
|
||||
|
||||
|
||||
def getAsset(name):
|
||||
return os.path.join(os.path.dirname(__file__), 'assets', name)
|
||||
|
||||
|
||||
register(cleanup)
|
||||
|
||||
macro_process = Popen(['pythonw',
|
||||
'macro.py']) # it serves to run macro.py in the background because thread make the recording slower for some reasons
|
||||
|
||||
# Window Setup
|
||||
window = Tk()
|
||||
window.title("PyMacroRecord")
|
||||
w = 350
|
||||
h = 200
|
||||
ws = window.winfo_screenwidth()
|
||||
hs = window.winfo_screenheight()
|
||||
x = (ws / 2) - (w / 2)
|
||||
y = (hs / 2) - (h / 2)
|
||||
window.geometry('%dx%d+%d+%d' % (w, h, x, y))
|
||||
|
||||
window.iconbitmap("assets/logo.ico")
|
||||
window.resizable(False, False)
|
||||
|
||||
# Menu Setup
|
||||
my_menu = Menu(window)
|
||||
window.config(menu=my_menu)
|
||||
|
||||
# File Section
|
||||
file_menu = Menu(my_menu, tearoff=0)
|
||||
my_menu.add_cascade(label="File", menu=file_menu)
|
||||
file_menu.add_command(label="New", state=DISABLED, accelerator="Ctrl+N")
|
||||
file_menu.add_command(label="Load", command=loadMacro, accelerator="Ctrl+L")
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Save", state=DISABLED, accelerator="Ctrl+S")
|
||||
file_menu.add_command(label="Save as", state=DISABLED, accelerator="Ctrl+Shift+S")
|
||||
|
||||
# Options Section
|
||||
options_menu = Menu(my_menu, tearoff=0)
|
||||
my_menu.add_cascade(label="Options", menu=options_menu)
|
||||
|
||||
# Playback Sub
|
||||
playback_sub = Menu(options_menu, tearoff=0)
|
||||
options_menu.add_cascade(label="Playback", menu=playback_sub)
|
||||
playback_sub.add_command(label="Speed", command=changeSpeed)
|
||||
playback_sub.add_command(label="Repeat", command=repeatGuiSettings)
|
||||
|
||||
# Recordings Sub
|
||||
mouseMove = BooleanVar(value=userSettings["Recordings"]["Mouse_Move"])
|
||||
mouseClick = BooleanVar(value=userSettings["Recordings"]["Mouse_Click"])
|
||||
keyboardInput = BooleanVar(value=userSettings["Recordings"]["Keyboard"])
|
||||
recordings_sub = Menu(options_menu, tearoff=0)
|
||||
options_menu.add_cascade(label="Recordings", menu=recordings_sub)
|
||||
recordings_sub.add_checkbutton(label="Mouse movement", variable=mouseMove,
|
||||
command=lambda: changeSettings("Recordings", "Mouse_Move"))
|
||||
recordings_sub.add_checkbutton(label="Mouse click", variable=mouseClick,
|
||||
command=lambda: changeSettings("Recordings", "Mouse_Click"))
|
||||
recordings_sub.add_checkbutton(label="Keyboard", variable=keyboardInput,
|
||||
command=lambda: changeSettings("Recordings", "Keyboard"))
|
||||
|
||||
# Options Sub
|
||||
options_sub = Menu(options_menu, tearoff=0)
|
||||
options_menu.add_cascade(label="Options", menu=options_sub)
|
||||
options_sub.add_command(label="Hotkeys", command=hotkeySettingsGui)
|
||||
|
||||
minimization_sub = Menu(options_sub, tearoff=0)
|
||||
options_sub.add_cascade(label="Minimization", menu=minimization_sub)
|
||||
minimization_playing = BooleanVar(value=userSettings["Minimization"]["When_Playing"])
|
||||
minimization_record = BooleanVar(value=userSettings["Minimization"]["When_Recording"])
|
||||
minimization_sub.add_checkbutton(label="Minimized when playing", variable=minimization_playing,
|
||||
command=lambda: changeSettings("Minimization", "When_Playing"))
|
||||
minimization_sub.add_checkbutton(label="Minimized when recording", variable=minimization_record,
|
||||
command=lambda: changeSettings("Minimization", "When_Recording"))
|
||||
|
||||
runStartUp = BooleanVar().set(userSettings["Run_On_StartUp"])
|
||||
# options_sub.add_checkbutton(label="Run on startup", variable=runStartUp, command=lambda: changeSettings("Run_On_StartUp"))
|
||||
options_sub.add_command(label="After playback...", command=afterPlaybackGui)
|
||||
|
||||
# Help section
|
||||
help_section = Menu(my_menu, tearoff=0)
|
||||
my_menu.add_cascade(label="Help", menu=help_section)
|
||||
help_section.add_command(label="Github Page",
|
||||
command=lambda: OpenUrl("https://github.com/LOUDO56/PyMacroRecord/blob/main/TUTORIAL.md"))
|
||||
help_section.add_command(label="About", command=aboutMeGui)
|
||||
|
||||
# Play Button
|
||||
playImg = PhotoImage(file=getAsset(f"button{os.sep}play.png"))
|
||||
playBtn = Button(window, image=playImg, command=startPlayback, state=DISABLED)
|
||||
playBtn.pack(side=LEFT, padx=50)
|
||||
|
||||
# Record Button
|
||||
recordImg = PhotoImage(file=getAsset(f"button{os.sep}record.png"))
|
||||
recordBtn = Button(window, image=recordImg, command=startRecordingAndChangeImg)
|
||||
recordBtn.pack(side=RIGHT, padx=50)
|
||||
|
||||
# Stop Button
|
||||
stopImg = PhotoImage(file=getAsset(f"button{os.sep}stop.png"))
|
||||
|
||||
window.bind('<Control-Shift-S>', saveMacroAs)
|
||||
window.bind('<Control-s>', saveMacro)
|
||||
window.bind('<Control-l>', loadMacro)
|
||||
window.bind('<Control-n>', newMacro)
|
||||
|
||||
keyboardListener = keyboard.Listener(on_press=on_press, on_release=on_release, win32_event_filter=win32_event_filter)
|
||||
keyboardListener.start()
|
||||
|
||||
validate_cmd = window.register(validate_input)
|
||||
|
||||
window.protocol("WM_DELETE_WINDOW", stopProgram)
|
||||
|
||||
Thread(target=systemTray).start()
|
||||
|
||||
if userSettings["Cant_rec"]:
|
||||
changeSettings("Cant_rec") # Prevents from conflicts
|
||||
|
||||
# Check updates
|
||||
newVersion = getVer("https://pastebin.com/raw/8YAjs4Pc").text
|
||||
if newVersion != version:
|
||||
versionUpToDate = "Outdated"
|
||||
newVerAvailable(newVersion)
|
||||
else:
|
||||
versionUpToDate = "Up to Date"
|
||||
|
||||
window.mainloop()
|
|
@ -0,0 +1,558 @@
|
|||
:root{
|
||||
--black-text: rgba(0, 0, 0, 0.788)
|
||||
}
|
||||
|
||||
*{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body{
|
||||
font-family: 'Poppins', sans-serif;
|
||||
color: var(--black-text);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
/* HEADER
|
||||
/*
|
||||
/*
|
||||
*/
|
||||
|
||||
|
||||
header{
|
||||
padding: 10px 20px;
|
||||
background-color: #F8F8F8;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
nav{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title{
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title img{
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.tired-of{
|
||||
max-width: 40em;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
margin-top: -20px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.title-software{
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--black-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.links{
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
font-size: 18px;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.links a{
|
||||
color: var(--black-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.links a:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.linked{
|
||||
color: #5db3f5
|
||||
}
|
||||
|
||||
.linked:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.links-mobile{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 40px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.hambuger-icon{
|
||||
width: 30px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.close-icon{
|
||||
float: right;
|
||||
margin-top: 17px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.links-mobile-menu{
|
||||
display: none;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 150px;
|
||||
background-color: #ebebeb;
|
||||
right: -25em;
|
||||
top: 0;
|
||||
transition: 200ms ease;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.links-mobile{
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
.links-mobile a{
|
||||
font-size: 20px;
|
||||
color: var(--black-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.donate{
|
||||
background-color: #6786F2;
|
||||
padding: 10px 15px;
|
||||
border-radius: 10px;
|
||||
color: white !important;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
transition: 200ms ease;
|
||||
|
||||
}
|
||||
|
||||
.donate:hover{
|
||||
text-decoration: none !important;
|
||||
background-color: #839dfa;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
/* MAIN
|
||||
/*
|
||||
/*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
.underline{
|
||||
display: inline-block;
|
||||
line-height: 0em;
|
||||
padding-bottom: 0.5em;
|
||||
background-color: #f0e582;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
.header-pres{
|
||||
margin-top: 10em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.big-title{
|
||||
font-size: 5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.presentation{
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.download{
|
||||
padding: 15px 20px;
|
||||
background-color: #6786F2;
|
||||
color: white;
|
||||
border-radius: 100px;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
transition: 200ms ease;
|
||||
}
|
||||
|
||||
.download:hover{
|
||||
background-color: #839dfa;
|
||||
}
|
||||
|
||||
|
||||
.not-windows{
|
||||
margin-top: -10px;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.important-point{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 50px;
|
||||
background-color: #273036;
|
||||
padding-bottom: 50px;
|
||||
color: rgba(255, 255, 255, 0.884);
|
||||
}
|
||||
|
||||
.box-pres{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6em;
|
||||
margin: 0 10px;
|
||||
|
||||
}
|
||||
|
||||
.box-pres.reversed{
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
|
||||
.box-title{
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 30px;
|
||||
margin-bottom: 30px;
|
||||
/* color:#98bfec; */
|
||||
text-shadow: 0 5px 5px rgba(0, 0, 0, 0.205);
|
||||
}
|
||||
|
||||
|
||||
.box-desc{
|
||||
font-size: 17px;
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
.key-word{
|
||||
color:#acd7ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.box-desc.spaced{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
|
||||
.no-premium{
|
||||
width: 290px;
|
||||
}
|
||||
|
||||
|
||||
.github-logo{
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.features-point{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 7em;
|
||||
background-color: #535f68;
|
||||
padding: 40px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.features-txt{
|
||||
font-size: 70px;
|
||||
color: #f7cc78;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 5px 5px rgba(0, 0, 0, 0.205);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
/* ABOUT ME
|
||||
/*
|
||||
/*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
.about-me{
|
||||
padding: 40px;
|
||||
|
||||
}
|
||||
|
||||
.about-me-txt{
|
||||
font-size: 70px;
|
||||
color: #629CF4;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.about-me-desc{
|
||||
font-size: 18px;
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.about-me-desc:last-child{
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.support{
|
||||
padding: 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.support-txt{
|
||||
font-size: 70px;
|
||||
color: #e83f88;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.support-desc{
|
||||
font-size: 18px;
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
/*
|
||||
/* DOWNLOAD PAGE
|
||||
/*
|
||||
/*
|
||||
*/
|
||||
|
||||
.information-download{
|
||||
margin: 7em 15em 0 15em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.download-title{
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.download-portable{
|
||||
margin-top: 25px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.download-desc{
|
||||
max-width: 70em;
|
||||
}
|
||||
|
||||
.warning-support{
|
||||
font-size: 19px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* I know i can just set margin on the download button but for some reason it's nor working.... */
|
||||
|
||||
.download-software > .download-desc{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
#installation-not-windows{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.file-explorer{
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
code{
|
||||
background-color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.pip-command, .more-info{
|
||||
margin-left: 35px;
|
||||
}
|
||||
|
||||
.ready{
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
/* OTHER
|
||||
/*
|
||||
/*
|
||||
*/
|
||||
|
||||
footer{
|
||||
margin-top: 50px;
|
||||
padding: 20px;
|
||||
background-color: #D9D9D9;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
|
||||
.box-pres, .box-pres.reversed{
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.features-point{
|
||||
gap: 70px;
|
||||
}
|
||||
|
||||
.box-pres img{
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.preview-software{
|
||||
width: 300px !important;
|
||||
}
|
||||
|
||||
.header-pres{
|
||||
margin-top: 7em;
|
||||
}
|
||||
|
||||
.links-mobile-menu{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.hambuger-icon{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.links{
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 668px) {
|
||||
|
||||
.title-software{
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.header-pres{
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.big-title{
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.logo-pres{
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.presentation{
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
.tired-of{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.about-me-txt, .support-txt{
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.about-me-desc, .support-desc{
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.features-txt{
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
.download-title{
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.download-desc{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.warning-support{
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#installation-not-windows{
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.file-explorer{
|
||||
width: 275px;
|
||||
}
|
||||
|
||||
.download.download-page{
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
footer{
|
||||
font-size: 14px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: 1056px) {
|
||||
|
||||
.information-download{
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|