+
     vi>  c                   s   R t ^ RIt^ RIt^ RIHt ^ RIHtHtHt ^ RI	H
t
 ^ RIHt ^ RIt^ RIHt ]P"                  ! ]4      t^t] ! R R]4      4       t ! R	 R
4      t ! R R]4      t ! R R]]R7      tR"R ltR tR#R ltR"R ltR"R ltR tR$R ltR t R t!RRRR/R lt"R#R lt#R%R R llt$]! RR 7      R! 4       t%R# )&zAssorted utilities used by drivers and the CLI.

Copyright Jonas Malaco and contributors
SPDX-License-Identifier: GPL-3.0-or-later
N)literal_eval)EnumEnumMetaunique)Optional)	lru_cache)UnsafeFeaturesNotEnabledc                   s   a  ] tR t^t o RtRtR	tR
tRtRt	Rt
RtRtRtRtRtRtRtRtRtRtRtR t]R 4       tR tR tRtV tR# )Hue2Accessorya  Mapping of HUE 2 accessory IDs and names.

>>> Hue2Accessory(4)
<Hue2Accessory.HUE2_LED_STRIP_300: 4>
>>> str(Hue2Accessory(4))
'HUE 2 LED Strip 300 mm'

Unknown IDs are automatically translated to equivalent pseudo-names.

>>> Hue2Accessory(59)
<Hue2Accessory.UNKNOWN_59: 59>
>>> Hue2Accessory(59).value == Hue2Accessory(59).value
True
>>> Hue2Accessory(59) != Hue2Accessory(58)
True
c                sH    \         P                  V 4      pW#n        Wn        V# N)object__new__pretty_name_value_)clsvaluer   Zmember   &&& 3/usr/lib/python3.14/site-packages/liquidctl/util.pyr
   ZHue2Accessory.__new__=   s     $(    c                s^    \         P                  V 4      pR Vn        RV 2Vn        Wn        V# )ZUnknownZUNKNOWN_)r	   r
   r   Z_name_r   )r   r   Zdummys   && r   	_missing_ZHue2Accessory._missing_C   s0    s#%!%)r   c                s    V P                   # r   )r   )self   &r   __str__ZHue2Accessory.__str__K   s    r   c                s4    V P                   VP                   8H  # r   )r   )r   Zother   &&r   __eq__ZHue2Accessory.__eq__N   s    zzU[[((r    N)   zHUE+ LED Strip)   z	AER RGB 1)i   zHUE 2 LED Strip 300 mm)i   zHUE 2 LED Strip 250 mm)i   zHUE 2 LED Strip 200 mm)i   zHUE 2 Cable Comb)i	   zHUE 2 Underglow 300 mm)i
   zHUE 2 Underglow 200 mm)i   zAER RGB 2 120 mm)i   zAER RGB 2 140 mm)i   z$Kraken X (X53, X63 or X73) Pump Ring)i   z$Kraken X (X53, X63 or X73) Pump Logo)i   zF120 RGB)i   zF140 RGB)i   zF140 RGB Core)i   zF240 RGB Core)i   zF360 RGB Core)__name__
__module____qualname____firstlineno____doc__ZHUE_PLUS_LED_STRIPZAER_RGB1_FANZHUE2_LED_STRIP_300ZHUE2_LED_STRIP_250ZHUE2_LED_STRIP_200ZHUE2_CABLE_COMBZHUE2_UNDERGLOW_300ZHUE2_UNDERGLOW_200ZAER_RGB2_120ZAER_RGB2_140ZKRAKENX_GEN4_RINGZKRAKENX_GEN4_LOGOZF120_RGBZF140_RGBZF140_RGB_COREZF240_RGB_COREZF360_RGB_COREr
   Zclassmethodr   r   r   __static_attributes____classdictcell____classdict__   @r   r   r      s     " 2&L9990O99-L-LFF!H!H+M+M+M   ) )r   r   c                   s4   a  ] tR t^Rt o RtRR ltR tRtV tR# )LazyHexReprah  Wrap an indexed collection of bytes with a lazy hex __repr__.

This is useful for logging, which uses `%` string formatting to lazily
generate the messages, only when needed.

>>> '%r' % LazyHexRepr(b'abc')
'61:62:63'

Start and end indices may also be specified.

>>> '%r' % LazyHexRepr(b'abc', start=1)
'62:63'
>>> '%r' % LazyHexRepr(b'abc', end=-1)
'61:62'
Nc                s6    Wn         W n        W0n        W@n        R # r   )datastartendsep)r   r&   r'   r(   r)   s   &&&&&r   __init__ZLazyHexRepr.__init__b   s    	
r   c                s    \        R  V P                  V P                  V P                   4      pV P                  P                  V4      # )c                 s
    V R  # )Z02xr   )xr   r   <lambda>Z&LazyHexRepr.__repr__.<locals>.<lambda>i   s
    1S'
r   )mapr&   r'   r(   r)   Zjoin)r   Zhexvalss   & r   __repr__ZLazyHexRepr.__repr__h   s5    *DIIdjj$((,KLxx}}W%%r   )r&   r(   r)   r'   )NN:)	r   r   r   r   r   r*   r.   r    r!   r"   r$   r   r%   r%   R   s     & &r   r%   c                   s2   a a ] tR t^mt oV 3R ltRtVtV ;t# )_RelaxedNamesEnumc                s>   < \         SV `  VP                  4       4      # r   )Zsuper__getitem__upper)r   Zname	__class__s   &&r   r1   Z_RelaxedNamesEnum.__getitem__n   s    w"4::<00r   r   )r   r   r   r   r1   r    r!   Z__classcell__)r3   r#   s   @@r   r0   r0   m   s     1 1r   r0   c                   s    ] tR t^rtRtRtR# )RelaxedNamesEnumz1Enum class where name lookup is case insensitive.r   N)r   r   r   r   r   r    r   r   r   r4   r4   r   s    ;r   r4   )Z	metaclassc                s\    V\        V 4      ,
          pV P                  V.V,          4       V # )zPad `list` with `fillitem` to `width`.

>>> rpadlist([1, 2, 3], 5)
[1, 2, 3, 0, 0]
>>> rpadlist([1, 2, 3], 5, fillitem=None)
[1, 2, 3, None, None]
)lenZextend)listZwidthZfillitemZ	pad_widthr   r   rpadlistr7   w   s*     D	!IKK
Y&'Kr   c                sj    \        V\        W 4      4      pW08w  d   \        P                  RWV4       V# )z;Clamp numeric `value` to interval [`clampmin`, `clampmax`].zclamped %s to interval [%s, %s])ZmaxZmin_LOGGERZdebug)r   ZclampminZclampmaxZclampedr   r   clampr9      s/    (C01G7(SNr   c                s    Ve
   V^d,          p V e,   V ^ 8  g   V ^8  d   \        R4      h\        V ^,          4      # \        R4      h)zReturn `ratio` xor `percentage` expressed as a fraction of 255.

>>> fraction_of_byte(ratio=.8)
204
>>> fraction_of_byte(percentage=20)
51
z'cannot express ratios outside of [0, 1]z+either ratio or percentage must not be None)
ValueErrorround)ZratioZ
percentager   r   fraction_of_byter<      sN     S 19	FGGUS[!!
B
CCr   c                B    \         P                  WV^,            RR7      # )zRead an unsigned 16-bit little-endian integer from `buffer`.

>>> u16le_from(b'E')
1349
>>> u16le_from(b'E', offset=1)
773
ZlittleZ	byteorderintZ
from_bytesZbufferZoffsetr   r   
u16le_fromrB      s      >>&!4>IIr   c                r=   )z~Read an unsigned 16-bit big-endian integer from `buffer`.

>>> u16be_from(b'E')
17669
>>> u16be_from(b'E', offset=1)
1283
Zbigr>   r?   rA   r   r   
u16be_fromrC      s      >>&!4>FFr   c                s    \        V R,          V RR 4       UUu. uF3  w  rV^ ,          V^ ,          ,
          V^,          V^,          ,
          3NK5  	  upp# u uppi )u    Compute a profile's Δx and Δy.r   NNN)zip)profileZcurZprevs   &  r   deltarH      sY     !gcrl;=;	 VDG^SVDG^,;= = =s   9Ac                s    \        \        V 4      W3.,           R R7      p V R,          p\        V R,          V RR 4       F5  w  w  rEw  rgWF8X  d   K  WW8  d   TpVP                  WE34       WR8X  g   K4   V# 	  V# )a  Normalize a [(x:int, y:int), ...] profile.

The normalized profile will ensure that:

 - the profile is a monotonically increasing function
   (i.e. for every i, i > 1, x[i] - x[i-1] > 0 and y[i] - y[i-1] >= 0)
 - the profile is sorted
 - a (critx, 100) failsafe is enforced
 - only the first point that sets y := 100 is kept

>>> normalize_profile([(30, 40), (25, 25), (35, 30), (40, 35), (40, 80)], 60)
[(25, 25), (30, 40), (35, 40), (40, 80), (60, 100)]
>>> normalize_profile([(30, 40), (25, 25), (35, 30), (40, 100)], 60)
[(25, 25), (30, 40), (35, 40), (40, 100)]
>>> normalize_profile([(30, 40), (25, 25), (35, 100), (40, 100)], 60)
[(25, 25), (30, 40), (35, 100)]
>>> normalize_profile([], 60)
[(60, 100)]
>>> normalize_profile([], 60, 300)
[(60, 300)]

c                 s(    V ^ ,          V ^,          ) 3#     r   )pr   r   r,   Z#normalize_profile.<locals>.<lambda>   s    !A$QRSTQUPUr   key:rJ   r   NrD   NrE   )sortedr6   rF   Zappend)rG   ZcritxZ	max_valueZmonor+   ZyZxbZybs   &&&     r   normalize_profilerO      s    . T']u&8%99?VWG3<DWSb\:76AQF>K ; Kr   c                sr   V ^ ,          V R,          r2V  F%  pV^ ,          V8:  d   TpV^ ,          V8  g   K#  Tp M	  V^ ,          V^ ,          8X  d
   V^,          # \        V^,          W^ ,          ,
          V^ ,          V^ ,          ,
          ,          V^,          V^,          ,
          ,          ,           4      # )a  Interpolate y given x and a [(x: int, y: int), ...] profile.

Requires the profile to be sorted by x, with no duplicate x values (see
normalize_profile).  Expects profiles with integer x and y values, and
returns duty rounded to the nearest integer.

>>> interpolate_profile([(20, 50), (50, 70), (60, 100)], 33)
59
>>> interpolate_profile([(20, 50), (50, 70)], 19)
50
>>> interpolate_profile([(20, 50), (50, 70)], 51)
70
>>> interpolate_profile([(20, 50)], 20)
50
rE   r;   )rG   r+   lowerr2   Zsteps   &&   r   interpolate_profilerR      s      1:wr{57a<E7a<E  Qx58QxqQq\E!HuQx,?@%(USTXBUVVWWr   c                s  a  V 3R lpS P                  4       pVP                  R4      '       d   V! S R,          R4      w  r4pW4V.# VP                  R4      '       dU   V! S R,          R4      w  rgp\        \        R \        P
                  ! VR,          V^d,          V^d,          4      4      4      # VP                  R4      '       dU   V! S R,          R4      w  rgp	\        \        R \        P                  ! VR,          V	^d,          V^d,          4      4      4      # \        S 4      ^8X  d   \        \        P                  S 4      4      # \        S 4      ^8X  d=   S P                  R	4      '       d&   \        \        P                  S R
,          4      4      # \        S 4      ^8X  d=   VP                  R4      '       d&   \        \        P                  S R,          4      4      # \        RS  24      h)u  Parse a color, and, if necessary, translate it into the RGB model.

The input string can be encoded in several formats:

 - ffffff: hexadecimal RGB implicit tuple (with or without the prefix '0x')
 - rgb(255, 255, 255): explicit RGB, R,G,B ∊ [0, 255]
 - hsv(360, 100, 100): explicit HSV, H ∊ [0, 360], SV ∊ [0, 100]
 - hsl(360, 100, 100): explicit HSL, H ∊ [0, 360], SV ∊ [0, 100]

>>> color_from_str('fF7f3f')
[255, 127, 63]
>>> color_from_str('0xfF7f3f')
[255, 127, 63]
>>> color_from_str('0XfF7f3f')
[255, 127, 63]
>>> color_from_str('#fF7f3f')
[255, 127, 63]
>>> color_from_str('Rgb(255, 127, 63)')
[255, 127, 63]
>>> color_from_str('Hsv(20, 75, 100)')
[255, 128, 64]
>>> color_from_str('Hsl(20, 100, 62)')
[255, 126, 61]

>>> color_from_str('fF7f3f1f')
Traceback (most recent call last):
    ...
ValueError: cannot parse color: fF7f3f1f
>>> color_from_str('0bff00ff')
Traceback (most recent call last):
    ...
ValueError: cannot parse color: 0bff00ff
>>> color_from_str('rgb()')
Traceback (most recent call last):
    ...
ValueError: expected 3-element triple: rgb()
>>> color_from_str('rgb(255)')
Traceback (most recent call last):
    ...
ValueError: expected 3-element triple: rgb(255)
>>> color_from_str('rgb(300, 255, 255)')
Traceback (most recent call last):
    ...
ValueError: expected value in range [0, 255]: 300 in rgb(300, 255, 255)
>>> color_from_str('hsv(360, 150, 100)')
Traceback (most recent call last):
    ...
ValueError: expected value in range [0, 100]: 150 in hsv(360, 150, 100)
>>> color_from_str('hsl(360, 100, 150)')
Traceback (most recent call last):
    ...
ValueError: expected value in range [0, 100]: 150 in hsl(360, 100, 150)
c           	      sj  < \        V 4      p\        V\        4      '       d   \        V4      ^8w  d   \	        RS 24      h\        W!4       Fc  w  r4\        V\        4      '       g(   \        V\        4      '       g   \	        RV RS 24      hV^ 8  g	   W48  g   KQ  \	        RV RV RS 24      h	  V# )   zexpected 3-element triple: zexpected float or int: z in zexpected value in range [0, z]: )r    Z
isinstancetupler5   r:   rF   r@   Zfloat)ZsubZ	maxvaluesZliteralr   Zmaxvaluer+   s   &&   r   parse_tripleZ$color_from_str.<locals>.parse_triple0  s    s#'5))S\Q->:1#>??"76OEeS))*UE2J2J #:5'aS!IJJqyE, #?zUGSWXYWZ![\\	  7
 r   zrgb(:rS   NNzhsv(h  c                 &    \        V ^,          4      #    rP   br   r   r,    color_from_str.<locals>.<lambda>B      %#,r   zhsl(c                 rW   rX   rP   rZ   r   r   r,   r\   E  r]   r   Z#rD   Z0x:r   NNzcannot parse color: )rY   rY   rY   )rV   d   r^   )rQ   
startswithr6   r-   colorsysZ
hsv_to_rgbZ
hls_to_rgbr5   ZbytesZfromhexr:   )
r+   rU   ZxlZrZgr[   ZhZsZvZls
   f         r   color_from_strra      s   n	 
B	}}Vquo6aay	v		quo6aC.0C0CAcE1S5RSTWRW0XYZZ	v		quo6aC.0C0CAcE1S5RSTWRW0XYZZ	Q1EMM!$%%	Q1c**EMM!B%())	Q1t,,EMM!B%())/s344r   unsafeerrorFc                s   a  S '       d   \        \        V 3R lV4      4      pV'       g   R# V'       d   \        V4      hR# )af  Check if unsafe feature requirements are met.

Unstable.

Checks if the requirements in the positional arguments (`*reqs`) are all
met by the `unsafe` string list of enabled features.

>>> check_unsafe('foo', unsafe='foo,bar')
True
>>> check_unsafe('foo', 'bar', unsafe='foo,bar')
True
>>> check_unsafe('foo', unsafe=None)
False
>>> check_unsafe('foo', 'baz', unsafe='foo,bar')
False

If `error=True` and some requirements have not been met, raises
`liquidctl.error.UnsafeFeaturesNotEnabled`.  In the default `error=False`
mode, a boolean is return indicating whether all requirements were met.

>>> check_unsafe('foo', 'baz', unsafe='foo,bar', error=True)
Traceback (most recent call last):
    ...
liquidctl.error.UnsafeFeaturesNotEnabled: required unsafe features have not been enabled: baz

In driver code, `unsafe` is normally passed in `**kwargs`.

>>> kwargs = {'unsafe': 'foo,bar'}
>>> check_unsafe('foo', 'bar', **kwargs)
True
>>> check_unsafe('foo', 'baz', error=True, **kwargs)
Traceback (most recent call last):
    ...
liquidctl.error.UnsafeFeaturesNotEnabled: required unsafe features have not been enabled: baz
c                 s   < V S9  # r   r   )r+   rb   s   &r   r,   Zcheck_unsafe.<locals>.<lambda>v  s	    avor   TF)rT   Zfilterr   )rb   rc   ZreqsZkwargss   d$*,r   check_unsaferd   P  s2    J V5t<=&t,,r   c                s    RP                  V 4      '       d   V# RP                  V 4      '       d   V# \        RV : 24      h)a  Check a `direction` option and run the appropriate closure.

Unstable.

Accepts both US and UK spellings.  Raises a `ValueError` if the option
value is neither forward[s] nor backward[s].

>>> map_direction('forward', 3, 42)
3
>>> map_direction('forwards', 3, 42)
3
>>> map_direction('backward', 3, 42)
42
>>> map_direction('backwards', 3, 42)
42
>>> map_direction('fooowwd', 3, 42)
Traceback (most recent call last):
    ...
ValueError: invalid direction: 'fooowwd'
ZforwardsZ	backwardszinvalid direction: )r_   r:   )Z	directionZforwardZbackwards   &&&r   map_directionre     sC    , Y''					*	*.ym<==r   c                sh    V ^8  d   QhR\         \        ,          R\         \        ,          R\        /# )r   r   max_fansZreturn)r   Zstrr@   dict)Zformats   "r   __annotate__rh     s3     l@ l@8C= l@HSM l@T l@r   c                sZ   V '       g   / # V P                  R4      p/ pV F  pVP                  R4      p\        V4      ^8w  d   \        R4      hV Uu. uF  qfP                  4       NK  	  upw  rx \	        V^
4      p	T	^ 8:  g   Te   Y8  d   \        RT R24      hTP                  4       R	9  d   \        RT R24      hTP                  YxP                  4       /4       K  	  \        \        VP                  4       R R7      4      # u upi   \         d    \        RT R24      hi ; i)
ax  Convert the --fan-mode=<key>:<value>[,...] options into a {key: value, ....} dictionary.
The default is an empty dictionary.

Unstable.

>>> fan_mode_parser(None, 5)
{}

>>> fan_mode_parser('', 5)
{}

>>> fan_mode_parser('1:dc', 5)
{'1': 'dc'}

>>> fan_mode_parser('2:auto', 5)
{'2': 'auto'}

>>> fan_mode_parser('3:off', 5)
{'3': 'off'}

>>> fan_mode_parser('1:DC', 5)
{'1': 'dc'}

>>> fan_mode_parser('2:AUTO', 5)
{'2': 'auto'}

>>> fan_mode_parser('3:OFF', 5)
{'3': 'off'}

>>> fan_mode_parser('5:OFF', 5)
{'5': 'off'}

>>> fan_mode_parser('5:OFF')
{'5': 'off'}

>>> fan_mode_parser('1:dc,2:auto,3:off,4:pwm', 5)
{'1': 'dc', '2': 'auto', '3': 'off', '4': 'pwm'}

>>> fan_mode_parser('1:dc, 2:auto, 3:off', 5)
{'1': 'dc', '2': 'auto', '3': 'off'}

>>> fan_mode_parser('4:dc, 3:auto, 2:off', 5)
{'2': 'off', '3': 'auto', '4': 'dc'}

>>> fan_mode_parser('1 :dc, 2: auto, 3 : off,    4       :     auto   ', 5)
{'1': 'dc', '2': 'auto', '3': 'off', '4': 'auto'}

>>> fan_mode_parser('1:dc:dc', 5)
Traceback (most recent call last):
    ...
ValueError: invalid format, should be '<fan num>:<mode>'

>>> fan_mode_parser('-1:dc', 5)
Traceback (most recent call last):
    ...
ValueError: invalid fan number: '-1'

>>> fan_mode_parser('0:pwm', 5)
Traceback (most recent call last):
    ...
ValueError: invalid fan number: '0'

>>> fan_mode_parser('5:dc', 4)
Traceback (most recent call last):
    ...
ValueError: invalid fan number: '5'

>>> fan_mode_parser('a:dc', 5)
Traceback (most recent call last):
    ...
ValueError: invalid fan number: 'a'


>>> fan_mode_parser('1:PMW', 5)
Traceback (most recent call last):
    ...
ValueError: invalid fan mode: 'PMW'

Z,r/   z,invalid format, should be '<fan num>:<mode>'zinvalid fan number: 'Z'zinvalid fan mode: 'c                 s    V ^ ,          # rI   r   )Zitemr   r   r,   Z!fan_mode_parser.<locals>.<lambda>
  s    d1gr   rL   )ZoffZautoZdcZpwm)
Zsplitr5   r:   Zstripr@   rQ   Zupdaterg   rN   Zitems)
r   rf   ZpartsZoptsrK   Zp2ZirM   ZvalZkey_vals
   &&        r   fan_mode_parserri     s   b 	KKEDWWS\r7a<KLL)+,Aggi,
	=#rlG a<H0W5G4SE;<<99;::23%q9::S))+&'% * tzz|)=>??! -  	=4SE;<<	=s   D
1DD*)Zmaxsizec                s@    \         P                  P                  V 4      # )a  Efficiently construct a predefined CRC function.

Unstable.

For the available algorithms, see
<http://crcmod.sourceforge.net/crcmod.predefined.html#predefined-crc-algorithms>.

This function implements memoization, only constructing each requested CRC
algorithm implementation once.
)crcmodZ
predefinedmkCrcFun)Zcrc_namer   r   rk   rk     s     %%h//r   rI   )NN)r^   r   )&r   r`   ZloggingZastr    Zenumr   r   r   Ztypingr   Z	functoolsr   Zcrcmod.predefinedrj   Zliquidctl.errorr   Z	getLoggerr   r8   ZHUE2_MAX_ACCESSORIES_IN_CHANNELr   r%   r0   r4   r7   r9   r<   rB   rC   rH   rO   rR   ra   rd   re   ri   rk   r   r   r   <module>rl      s       ' '    4


H
%"#  6)D 6) 6)r& &61 1
	t'8 	

D"JG=!HX8T5n-t -5 -`><l@^ 40 0r   