mirror of
https://codeberg.org/privacy1st/rotate-screen
synced 2024-11-22 21:39:33 +01:00
init
This commit is contained in:
commit
d6e11fcdf9
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.idea/
|
121
LICENSE
Normal file
121
LICENSE
Normal file
@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# RotateScreen
|
||||
|
||||
## Further ideas
|
||||
|
||||
```python
|
||||
def get_sensor_orientation():
|
||||
"""
|
||||
Get rotation from `monitor-sensor`.
|
||||
Example:
|
||||
- stdout: === Has accelerometer (orientation: left-up)
|
||||
- stdout: Accelerometer orientation changed: left-up
|
||||
"""
|
||||
```
|
110
de-p1st-rotate.py
Normal file
110
de-p1st-rotate.py
Normal file
@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import configparser
|
||||
import subprocess
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
|
||||
orientations = ['normal', 'right', 'inverted', 'left']
|
||||
|
||||
|
||||
class Screen(NamedTuple):
|
||||
name: str
|
||||
devices: list[str]
|
||||
|
||||
|
||||
def main():
|
||||
rotate_clockwise()
|
||||
|
||||
|
||||
def rotate_clockwise():
|
||||
cfg = get_cfg()
|
||||
|
||||
# screens from cfg that are connected
|
||||
screens = [Screen(name=name, devices=list(cfg[name].values()))
|
||||
for name in cfg
|
||||
if name != cfg.default_section
|
||||
and is_connected(name)]
|
||||
|
||||
if len(screens) == 0:
|
||||
raise Exception('None of the configured screens are connected.')
|
||||
|
||||
current_orientation = get_current_orientation(screens[0].name)
|
||||
next_orientation = orientations[(orientations.index(current_orientation) + 1) % len(orientations)]
|
||||
|
||||
for screen in screens:
|
||||
rotate(screen, next_orientation)
|
||||
|
||||
|
||||
def get_cfg() -> configparser.ConfigParser:
|
||||
config: configparser.ConfigParser = configparser.ConfigParser()
|
||||
config.read(get_cfg_path())
|
||||
return config
|
||||
|
||||
|
||||
def get_cfg_path() -> Path:
|
||||
global_path = Path('/etc/rotate-screen.cfg')
|
||||
if global_path.exists():
|
||||
return global_path
|
||||
|
||||
local_path = Path('example.cfg')
|
||||
if local_path.exists():
|
||||
return local_path
|
||||
|
||||
raise Exception('No configuration file found.')
|
||||
|
||||
|
||||
def rotate(screen: Screen, orientation):
|
||||
execute(['xrandr', '--output', screen.name, '--rotate', orientation])
|
||||
for device in screen.devices:
|
||||
execute(['xrandr', '--map-to-output', device, screen.name])
|
||||
|
||||
|
||||
def get_current_orientation(screen: str):
|
||||
"""
|
||||
@precond: is_connected(screen) = True
|
||||
|
||||
Example:
|
||||
- stdout includes line: eDP connected primary 2880x1620+0+0 (0x55) normal (normal left inverted right x axis y axis) 344mm x 194mm
|
||||
- screen: eDP
|
||||
- returns: normal
|
||||
"""
|
||||
stdout = execute(['xrandr', '--query', '--verbose'])
|
||||
pattern = re.compile(rf'^({re.escape(screen)}\s.*)$', flags=re.MULTILINE)
|
||||
match = pattern.search(stdout)
|
||||
if match is None: raise Exception(f'Did not find screen {screen} in stdout:\n{stdout}')
|
||||
line = match.group(1)
|
||||
return line.rsplit()[5]
|
||||
|
||||
|
||||
def is_connected(screen: str):
|
||||
"""
|
||||
Example:
|
||||
- stdout includes line: eDP connected primary 2880x1620+0+0 (normal left inverted right x axis y axis) 344mm x 194mm
|
||||
- returns: True
|
||||
"""
|
||||
stdout = execute(['xrandr'])
|
||||
pattern = re.compile(rf'^({re.escape(screen)}\sconnected\s.*)$', flags=re.MULTILINE)
|
||||
match = pattern.search(stdout)
|
||||
return match is not None
|
||||
|
||||
|
||||
def execute(command: list[str]) -> str:
|
||||
"""
|
||||
:return: stdout of command execution
|
||||
"""
|
||||
completed: subprocess.CompletedProcess = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise Exception(f'Exit Code: {completed.returncode}\n'
|
||||
f'Stdout:\n{completed.stdout}\n'
|
||||
f'Stderr:\n{completed.stderr}')
|
||||
return completed.stdout
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
13
example.cfg
Normal file
13
example.cfg
Normal file
@ -0,0 +1,13 @@
|
||||
[eDP]
|
||||
|
||||
[HDMI-A-0]
|
||||
|
||||
[eDP-1]
|
||||
|
||||
1 = pointer:ELAN9038:00 04F3:2A1C
|
||||
2 = pointer:ELAN9038:00 04F3:2A1C Stylus Pen (0)
|
||||
3 = pointer:ELAN9038:00 04F3:2A1C Stylus Eraser (0)
|
||||
|
||||
4 = pointer:ELAN9038:00 04F3:2A1C touch
|
||||
5 = pointer:ELAN9038:00 04F3:2A1C stylus
|
||||
6 = pointer:ELAN9038:00 04F3:2A1C eraser
|
Loading…
Reference in New Issue
Block a user