|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 |
|
3 |
| -# Copyright 2013 Ryan McGowan |
4 |
| -# |
5 |
| -# Licensed under the Apache License, Version 2.0 (the "License"); |
6 |
| -# you may not use this file except in compliance with the License. |
7 |
| -# You may obtain a copy of the License at |
8 |
| -# |
9 |
| -# http://www.apache.org/licenses/LICENSE-2.0 |
10 |
| -# |
11 |
| -# Unless required by applicable law or agreed to in writing, software |
12 |
| -# distributed under the License is distributed on an "AS IS" BASIS, |
13 |
| -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 |
| -# See the License for the specific language governing permissions and |
15 |
| -# limitations under the License. |
| 3 | +from argparse import ArgumentParser, BooleanOptionalAction |
| 4 | +from subprocess import call |
| 5 | +from sys import argv |
16 | 6 |
|
17 |
| -import sys |
18 |
| -import os |
19 |
| -import json |
20 |
| -from subprocess import call, check_output |
21 | 7 |
|
22 |
| -# to redirect xlib output |
23 |
| -# note that there is a dedicated method to do this in python >3.4 |
24 |
| -# called contextlib.redirect_stdout |
25 |
| -from contextlib import contextmanager |
26 |
| - |
27 |
| - |
28 |
| -def fileno(file_or_fd): |
29 |
| - fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)() |
30 |
| - if not isinstance(fd, int): |
31 |
| - raise ValueError("Expected a file (`.fileno()`) or a file descriptor") |
32 |
| - return fd |
33 |
| - |
34 |
| - |
35 |
| -@contextmanager |
36 |
| -def redirect(fro, to): |
37 |
| - |
38 |
| - stdout_fd = fileno(fro) |
39 |
| - # copy stdout_fd before it is overwritten |
40 |
| - # NOTE: `copied` is inheritable on Windows when duplicating a standard |
41 |
| - # stream |
42 |
| - with os.fdopen(os.dup(stdout_fd), 'wb') as copied: |
43 |
| - fro.flush() # flush library buffers that dup2 knows nothing about |
44 |
| - try: |
45 |
| - os.dup2(fileno(to), stdout_fd) # $ exec >&to |
46 |
| - except ValueError: # filename |
47 |
| - with open(to, 'wb') as to_file: |
48 |
| - os.dup2(to_file.fileno(), stdout_fd) # $ exec > to |
49 |
| - try: |
50 |
| - yield fro # allow code to be run with the redirected fro |
51 |
| - finally: |
52 |
| - # restore fro to its previous value |
53 |
| - # NOTE: dup2 makes stdout_fd inheritable unconditionally |
54 |
| - fro.flush() |
55 |
| - os.dup2(copied.fileno(), stdout_fd) # $ exec >&copied |
56 |
| - |
57 |
| - |
58 |
| -def parse_dmenu_args(args): |
59 |
| - |
60 |
| - split = args.index('--rest') if '--rest' in args else len(args) |
61 |
| - |
62 |
| - args, rest = args[:split], args[split + 1:] |
63 |
| - |
64 |
| - argc = len(args) |
65 |
| - # Get arguments from the command line. |
66 |
| - |
67 |
| - runnable = args[0] if argc > 0 else 'dmenu' |
68 |
| - |
69 |
| - # 20% padding means only 80% of the screen is used by dmenu with 10% |
70 |
| - # padding on each side. |
71 |
| - padding = float(args[1]) if argc > 1 else .24 |
72 |
| - |
73 |
| - # Font size and lineheight are in points |
74 |
| - line_height = int(args[2]) if argc > 2 else 24 |
75 |
| - |
76 |
| - font = args[3] if argc > 3 else 'LiterationMono Nerd Font Mono:12' |
77 |
| - |
78 |
| - with redirect(sys.stdout, sys.stderr): |
79 |
| - from Xlib import display |
80 |
| - current_display = display.Display() |
81 |
| - current_screen = current_display.screen() |
82 |
| - |
83 |
| - workspaces = json.loads(check_output(['i3-msg', '-t', 'get_workspaces'])) |
84 |
| - workspace = next(workspace for workspace in workspaces if workspace['focused']) |
85 |
| - |
86 |
| - screenwidth = workspace['rect']['width'] * 1.0 |
87 |
| - xoffset = int(round(padding / 2 * screenwidth)) |
88 |
| - width = screenwidth - 2 * xoffset |
89 |
| - |
90 |
| - wholeheight = current_screen['height_in_pixels'] * 1.0 |
91 |
| - screenheight = workspace['rect']['height'] * 1.0 |
92 |
| - s = screenheight / wholeheight |
93 |
| - height_in_mms = current_screen['height_in_mms'] * 1.0 |
94 |
| - height_in_inches = height_in_mms / 25.4 * s |
95 |
| - height_in_points = height_in_inches * 72 |
96 |
| - pixels_per_point = screenheight / height_in_points |
97 |
| - |
98 |
| - height = int(round(line_height * pixels_per_point, 0)) |
99 |
| - yoffset = int(round((screenheight - height) / 2)) |
100 |
| - height = screenheight - 2 * yoffset |
101 |
| - |
102 |
| - # Set some default values for dmenu args |
103 |
| - dmenu_run_args = { |
104 |
| - 'runnable': runnable, |
105 |
| - 'xoffset': str(xoffset), |
106 |
| - 'yoffset': str(yoffset), |
107 |
| - 'width': str(width), |
108 |
| - 'height': str(height), |
109 |
| - 'font': font, |
110 |
| - 'nb': '#1a1a1a' |
111 |
| - } |
112 |
| - |
113 |
| - return dmenu_run_args, rest |
114 |
| - |
115 |
| - |
116 |
| -def main(args): |
117 |
| - dmenu_run_args, rest = parse_dmenu_args(args) |
118 |
| - cmd = [dmenu_run_args['runnable']] |
119 |
| - cmd += ['-fn', dmenu_run_args['font']] |
120 |
| - cmd += ['-x', dmenu_run_args['xoffset']] |
121 |
| - cmd += ['-y', dmenu_run_args['yoffset']] |
122 |
| - cmd += ['-h', dmenu_run_args['height']] |
123 |
| - cmd += ['-w', dmenu_run_args['width']] |
124 |
| - cmd += ['-nb', dmenu_run_args['nb']] |
125 |
| - cmd.extend(rest) |
126 |
| - return call(cmd) |
127 |
| - |
128 |
| - |
129 |
| -def console_main(): |
130 |
| - sys.exit(main(sys.argv[1:])) |
| 8 | +css_variables = { |
| 9 | + 'fn': 'font', |
| 10 | + 'nb': 'normal background', |
| 11 | + 'nf': 'normal foreground', |
| 12 | + 'sb': 'selected background', |
| 13 | + 'sf': 'selected foreground', |
| 14 | + 'l': 'number of lines' |
| 15 | +} |
131 | 16 |
|
132 | 17 | if __name__ == '__main__':
|
133 |
| - console_main() |
| 18 | + |
| 19 | + parser = ArgumentParser() |
| 20 | + for key, value in css_variables.items(): |
| 21 | + parser.add_argument('-{}'.format(key), help=value) |
| 22 | + |
| 23 | + parser.add_argument('-t', help='match filter words separately', dest="tokenize", action="store_true") |
| 24 | + parser.add_argument('-tokenize', help='match filter words separately', action="store_true") |
| 25 | + parser.add_argument('-z', help='fuzzy matching', dest="matching", action="store_const", const="fuzzy") |
| 26 | + parser.add_argument('-matching', help='rofi\'s -matching option', default="normal") |
| 27 | + |
| 28 | + parser.add_argument('-sort', help='rofi\'s -sort/-no-sort option', action=BooleanOptionalAction, default=True) |
| 29 | + parser.add_argument('-sorting-method', help='rofi\'s -sorting-method option', default="fzf") |
| 30 | + |
| 31 | + parser.add_argument('-fullscreen', help='display menu in fullscreen', action="store_true") |
| 32 | + parser.add_argument('-mask', help='mask input', action="store_true") |
| 33 | + |
| 34 | + args, rest = parser.parse_known_intermixed_args(argv[1:]) |
| 35 | + kw = vars(args) |
| 36 | + |
| 37 | + css_variable_defs = ''.join([ |
| 38 | + '{}: {};'.format(key, value) for key in css_variables if (value := kw.get(key)) is not None |
| 39 | + ]) |
| 40 | + |
| 41 | + theme_str = '* {{{}}}'.format(css_variable_defs) |
| 42 | + location = '2' if args.fullscreen else None |
| 43 | + |
| 44 | + cmd: tuple[str] = ( |
| 45 | + 'rofi', |
| 46 | + '-dmenu', |
| 47 | + '-theme', |
| 48 | + 'fullscreen' if args.fullscreen else 'oneline' if args.l is None else 'multiline', |
| 49 | + *(('-location', location) if location is not None else ()), |
| 50 | + '-theme-str', |
| 51 | + theme_str, |
| 52 | + *(('-matching', args.matching) if args.matching is not None else ()), |
| 53 | + *(('-t',) if args.tokenize else ()), |
| 54 | + *(('-password',) if args.mask else ()), |
| 55 | + '-sort' if args.sort else '-no-sort', |
| 56 | + '-sorting-method', args.sorting_method, |
| 57 | + *rest |
| 58 | + ) |
| 59 | + |
| 60 | + call(cmd) |
0 commit comments