diff --git a/Xresources.d/configure b/Xresources.d/configure index 4d0d2d1..be91226 100755 --- a/Xresources.d/configure +++ b/Xresources.d/configure @@ -5,5 +5,4 @@ echo $(for i in "" "%20Bold" "%20Oblique" "%20Bold%20Oblique"; do cd $HOME/.local/share/fonts wget -c http://raw.githubusercontent.com/powerline/fonts/master/DejaVuSansMono/DejaVu%20Sans%20Mono$i%20for%20Powerline.ttf done) -wget -c "https://aur.archlinux.org/cgit/aur.git/plain/icons.ttf?h=ttf-font-icons" -O $HOME/.local/share/fonts/icons.ttf - +wget -c "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/a8386aae19e200ddb0f6845b5feeee5eb7013687/fonts/fontawesome-webfont.ttf" -O $HOME/.local/share/fonts/fontawesome-webfont.ttf diff --git a/config/i3/config b/config/i3/config index 65f223b..dee99d7 100644 --- a/config/i3/config +++ b/config/i3/config @@ -64,8 +64,8 @@ bindsym $mod+F11 exec urxvtc -e 'pacmixer' bindsym $mod+F12 exec urxvtc -e 'pacmixer' #Brightness control -bindsym XF86MonBrightnessDown exec xbacklight -dec 20 -bindsym XF86MonBrightnessUp exec xbacklight -inc 20 +bindsym XF86MonBrightnessDown exec xbacklight -dec 20 -time 0 +bindsym XF86MonBrightnessUp exec xbacklight -inc 20 -time 0 # Screenshots bindsym Print exec scrot -ue 'mv $f ~/Screenshots/' @@ -226,13 +226,6 @@ bindsym $mod+ctrl+shift+Left move workspace to output left bindsym $mod+Ctrl+Shift+Up move workspace to output above bindsym $mod+Ctrl+Shift+Down move workspace to output below -# Open applications on specific workspaces -assign [class="Thunderbird"] $WS7 -assign [class="Skype"] $WS7 -assign [class="Pidgin"] $WS7 -assign [class="Clementine"] $WS10 -assign [title="TweetDeck"] $WS8 - # Open specific applications in floating mode for_window [title="pacmixer"] floating enable border pixel 2 for_window [class="Firefox"] layout tabbed @@ -353,6 +346,7 @@ set_from_resource $color15 i3wm.color15 #cfd0c2 # Inactivity settings exec --no-startup-id xautolock -time 10 -locker 'xset dpms force standby' -killtime 1 -killer '$locker' +bindsym $mod+F1 exec --no-startup-id xset dpms force off bindsym $mod+F4 exec --no-startup-id xautolock -disable bindsym $mod+F5 exec --no-startup-id xautolock -enable @@ -366,7 +360,7 @@ exec --no-startup-id unclutter -root # Hide mouse cursor after some time #exec --no-startup-id dunst # Notifications (handled by systemd) exec --no-startup-id keynav # Keyboard cursor controller #exec --no-startup-id mpd # Music Player Daemon (handled by systemd) -exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill +# exec --no-startup-id ~/.config/i3/ashuffle # MPD Auto-refill exec --no-startup-id autorandr --change # Screen configuration and everything that depends on it exec --no-startup-id ~/.config/i3/batteryNotify -d # Battery state notification @@ -381,3 +375,7 @@ client.urgent $color01 $color01 $color07 $foreground $color09 client.placeholder $ignore $color06 $color07 $ignore $color14 client.background $color15 + +# bar { +# i3bar_command ~/.config/lemonbar/bar.py +# } diff --git a/config/lemonbar/bar.py b/config/lemonbar/bar.py index 962e797..12902ab 100755 --- a/config/lemonbar/bar.py +++ b/config/lemonbar/bar.py @@ -25,6 +25,7 @@ if __name__ == "__main__": Bar.addSectionAll(MpdProvider(theme=7), BarGroupType.LEFT) # Bar.addSectionAll(I3WindowTitleProvider(), BarGroupType.LEFT) + # TODO Computer modes SYSTEM_THEME = 2 DANGER_THEME = FOCUS_THEME @@ -40,6 +41,7 @@ if __name__ == "__main__": # TODO Disk space provider # TODO Screen (connected, autorandr configuration, bbswitch) provider Bar.addSectionAll(PulseaudioProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT) + Bar.addSectionAll(RfkillProvider(theme=PERIPHERAL_THEME), BarGroupType.RIGHT) Bar.addSectionAll(NetworkProvider(theme=NETWORK_THEME), BarGroupType.RIGHT) # Personal @@ -50,3 +52,5 @@ if __name__ == "__main__": TIME_THEME = 6 Bar.addSectionAll(TimeProvider(theme=TIME_THEME), BarGroupType.RIGHT) + + # Bar.run() diff --git a/config/lemonbar/display.py b/config/lemonbar/display.py index 9e63896..5e597f6 100755 --- a/config/lemonbar/display.py +++ b/config/lemonbar/display.py @@ -3,6 +3,9 @@ import enum import threading import time +import i3ipc +import os +import signal import subprocess import logging import coloredlogs @@ -10,6 +13,7 @@ import coloredlogs coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') log = logging.getLogger() + # TODO Allow deletion of Bar, BarGroup and Section for screen changes # IDEA Use i3 ipc events rather than relying on xrandr or Xlib (less portable # but easier) @@ -18,6 +22,7 @@ log = logging.getLogger() # TODO Use bytes rather than strings # TODO Use default colors of lemonbar sometimes # TODO Adapt bar height with font height +# TODO OPTI Static text objects that update its parents if modified class BarGroupType(enum.Enum): @@ -30,7 +35,7 @@ class BarGroupType(enum.Enum): class BarStdoutThread(threading.Thread): def run(self): - while True: + while Bar.running: handle = Bar.process.stdout.readline().strip() if not len(handle): continue @@ -52,6 +57,7 @@ class Bar: @staticmethod def init(): + Bar.running = True Section.init() cmd = ['lemonbar', '-b', '-a', '64'] @@ -66,10 +72,35 @@ class Bar: Bar(0) # Bar(1) + @staticmethod + def stop(): + Bar.running = False + Bar.process.kill() + + # TODO This is not really the best way to do it I guess + os.killpg(os.getpid(), signal.SIGTERM) + + @staticmethod + def run(): + Bar.forever() + i3 = i3ipc.Connection() + + def doStop(*args): + Bar.stop() + print(88) + + try: + i3.on('ipc_shutdown', doStop) + i3.main() + except BaseException: + print(93) + Bar.stop() + # Class globals everyone = set() string = "" process = None + running = False nextHandle = 0 actionsF2H = dict() @@ -92,8 +123,11 @@ class Bar: @staticmethod def forever(): - while True: - time.sleep(60) + try: + while True: + time.sleep(60) + except BaseException: + Bar.stop() def __init__(self, screen): assert isinstance(screen, int) @@ -136,15 +170,16 @@ class Bar: @staticmethod def updateAll(): - Bar.string = "" - for bar in Bar.everyone: - bar.update() - Bar.string += bar.string - # Color for empty sections - Bar.string += BarGroup.color(*Section.EMPTY) + if Bar.running: + Bar.string = "" + for bar in Bar.everyone: + bar.update() + Bar.string += bar.string + # Color for empty sections + Bar.string += BarGroup.color(*Section.EMPTY) - Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8')) - Bar.process.stdin.flush() + Bar.process.stdin.write(bytes(Bar.string + '\n', 'utf-8')) + Bar.process.stdin.flush() class BarGroup: @@ -276,11 +311,14 @@ class SectionThread(threading.Thread): class Section: # TODO Update all of that to base16 - COLORS = ['#002b36', '#dc322f', '#859900', '#b58900', '#268bd2', '#6c71c4', - '#2aa198', '#93a1a1', '#657b83', '#dc322f', '#859900', '#b58900', - '#268bd2', '#6c71c4', '#2aa198', '#fdf6e3'] - FGCOLOR = '#93a1a1' - BGCOLOR = '#002b36' + # COLORS = ['#272822', '#383830', '#49483e', '#75715e', '#a59f85', '#f8f8f2', + # '#f5f4f1', '#f9f8f5', '#f92672', '#fd971f', '#f4bf75', '#a6e22e', + # '#a1efe4', '#66d9ef', '#ae81ff', '#cc6633'] + COLORS = ['#181818', '#AB4642', '#A1B56C', '#F7CA88', '#7CAFC2', '#BA8BAF', + '#86C1B9', '#D8D8D8', '#585858', '#AB4642', '#A1B56C', '#F7CA88', + '#7CAFC2', '#BA8BAF', '#86C1B9', '#F8F8F8'] + FGCOLOR = '#F8F8F2' + BGCOLOR = '#272822' THEMES = list() EMPTY = (FGCOLOR, BGCOLOR) @@ -357,6 +395,8 @@ class Section: def updateText(self, text): if isinstance(text, str): text = Text(text) + elif isinstance(text, Text) and not len(text.elements): + text = None self.dstText[0] = None if (text is None and not self.persistent) else ((' ' + self.icon + ' ') if self.icon else ' ') self.dstText[1] = text @@ -373,6 +413,12 @@ class Section: Section.sizeChanging.add(self) Section.somethingChanged.set() + def setDecorators(self, **kwargs): + self.dstText.setDecorators(**kwargs) + self.curText = str(self.dstText) + self.informParentsTextChanged() + Section.somethingChanged.set() + def updateTheme(self, theme): assert isinstance(theme, int) assert theme < len(Section.THEMES) @@ -440,13 +486,14 @@ class Section: class StatefulSection(Section): # TODO FEAT Allow to temporary expand the section (e.g. when important change) NUMBER_STATES = None + DEFAULT_STATE = 0 def __init__(self, *args, **kwargs): Section.__init__(self, *args, **kwargs) - self.state = 0 + self.state = self.DEFAULT_STATE if hasattr(self, 'onChangeState'): self.onChangeState(self.state) - self.dstText.setDecorators(clickLeft=self.incrementState, + self.setDecorators(clickLeft=self.incrementState, clickRight=self.decrementState) def incrementState(self): @@ -466,6 +513,9 @@ class StatefulSection(Section): self.refreshData() class ColorCountsSection(StatefulSection): + # TODO FEAT Blend colors when not expanded + # TODO FEAT Blend colors with importance of count + # TODO FEAT Allow icons instead of counts NUMBER_STATES = 3 COLORABLE_ICON = '?' @@ -552,6 +602,8 @@ class Text: nest('A' + number + ':' + handle.decode() + ':', 'A' + number) for key, val in self.decorators.items(): + if val is None: + continue if key == 'fg': reset = self.section.THEMES[self.section.theme][0] nest('F' + getColor(val), 'F' + reset) diff --git a/config/lemonbar/launch.sh b/config/lemonbar/launch.sh new file mode 100755 index 0000000..01267ff --- /dev/null +++ b/config/lemonbar/launch.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env sh + +# TODO Make this better + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +ex="$DIR/bar.py" + +# Terminate already running bar instances +ps -af | grep "python3 $ex" | grep -v grep | awk '{print $2}' | while read p; do kill $p; done +killall -q lemonbar + +$ex + + diff --git a/config/lemonbar/providers.py b/config/lemonbar/providers.py index 2856ab3..bd26bcd 100755 --- a/config/lemonbar/providers.py +++ b/config/lemonbar/providers.py @@ -18,6 +18,8 @@ import random coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') log = logging.getLogger() +# TODO Generator class (for I3WorkspacesProvider, NetworkProvider and later +# PulseaudioProvider and MpdProvider) def humanSize(num): """ @@ -43,6 +45,7 @@ class TimeProvider(StatefulSection, PeriodicUpdater): "%d/%m %H:%M:%S", "%a %d/%m/%y %H:%M:%S"] NUMBER_STATES = len(FORMATS) + DEFAULT_STATE = 1 def fetcher(self): now = datetime.datetime.now() @@ -114,13 +117,13 @@ class RamProvider(AlertingSection, PeriodicUpdater): def fetcher(self): mem = psutil.virtual_memory() - freePerc = 1-mem.percent/100 + freePerc = mem.percent/100 self.updateLevel(freePerc) if self.state < 1: return None - text = Text(Section.ramp(freePerc)) + text = Text(Section.ramp(1-freePerc)) if self.state >= 2: freeStr = humanSize(mem.available) text.append(freeStr) @@ -162,7 +165,8 @@ class TemperatureProvider(AlertingSection, PeriodicUpdater): class BatteryProvider(AlertingSection, PeriodicUpdater): - NUMBER_STATES = 2 + # TODO Support ACPID for events + NUMBER_STATES = 3 RAMP = "" def fetcher(self): @@ -179,8 +183,15 @@ class BatteryProvider(AlertingSection, PeriodicUpdater): if self.state < 1: return - return Text('{:.0f}%'.format(bat.percent)) - # TODO Time remaining (if the estimation is somewhat correct) + t = Text('{:.0f}%'.format(bat.percent)) + + if self.state < 2: + return t + + h = int(bat.secsleft / 3600) + m = int((bat.secsleft - h * 3600) / 60) + t.append(" ({:d}:{:02d})".format(h, m)) + return t def __init__(self, theme=None): AlertingSection.__init__(self, theme) @@ -189,10 +200,13 @@ class BatteryProvider(AlertingSection, PeriodicUpdater): -class PulseaudioProvider(Section, ThreadedUpdater): +class PulseaudioProvider(StatefulSection, ThreadedUpdater): + NUMBER_STATES = 3 + DEFAULT_STATE = 1 + def __init__(self, theme=None): ThreadedUpdater.__init__(self) - Section.__init__(self, theme) + StatefulSection.__init__(self, theme) self.pulseEvents = pulsectl.Pulse('event-handler') self.pulseEvents.event_mask_set(pulsectl.PulseEventMaskEnum.sink) @@ -205,20 +219,32 @@ class PulseaudioProvider(Section, ThreadedUpdater): sinks = [] with pulsectl.Pulse('list-sinks') as pulse: for sink in pulse.sink_list(): - vol = pulse.volume_get_all_chans(sink) - if vol > 1: - vol = 1 - if sink.port_active.name == "analog-output-headphones": icon = "" elif sink.port_active.name == "analog-output-speaker": - icon = "" + icon = "" if sink.mute else "" else: icon = "?" + vol = pulse.volume_get_all_chans(sink) + fg = (sink.mute and '#333333') or (vol > 1 and '#FF0000') or None - ramp = "" if sink.mute else (" " + self.ramp(vol)) - sinks.append(icon + ramp) - return " ".join(sinks) + t = Text(icon, fg=fg) + sinks.append(t) + + if self.state < 1: + continue + + if self.state < 2: + if not sink.mute: + ramp = " " + while vol >= 0: + ramp += self.ramp(vol if vol < 1 else 1) + vol -= 1 + t.append(ramp) + else: + t.append(" {:2.0f}%".format(vol*100)) + + return Text(*sinks) def loop(self): self.pulseEvents.event_listen() @@ -230,6 +256,7 @@ class PulseaudioProvider(Section, ThreadedUpdater): class NetworkProviderSection(StatefulSection, Updater): NUMBER_STATES = 5 + DEFAULT_STATE = 1 def actType(self): self.ssid = None @@ -260,6 +287,8 @@ class NetworkProviderSection(StatefulSection, Updater): return ipv4, ipv6 def fetcher(self): + self.icon = None + self.persistent = False if self.iface not in self.parent.stats or \ not self.parent.stats[self.iface].isup or \ self.iface.startswith('lo'): @@ -364,6 +393,41 @@ class NetworkProvider(Section, PeriodicUpdater): self.fetchData() self.changeInterval(5) +class RfkillProvider(Section, PeriodicUpdater): + # TODO FEAT rfkill doesn't seem to indicate that the hardware switch is + # toggled + PATH = '/sys/class/rfkill' + + def fetcher(self): + t = Text() + for device in os.listdir(self.PATH): + with open(os.path.join(self.PATH, device, 'soft'), 'rb') as f: + softBlocked = f.read().strip() != b'0' + with open(os.path.join(self.PATH, device, 'hard'), 'rb') as f: + hardBlocked = f.read().strip() != b'0' + + if not hardBlocked and not softBlocked: + continue + + with open(os.path.join(self.PATH, device, 'type'), 'rb') as f: + typ = f.read().strip() + + fg = (hardBlocked and '#CCCCCC') or (softBlocked and '#FF0000') + if typ == b'wlan': + icon = '' + elif typ == b'bluetooth': + icon = '' + else: + icon = '?' + + t.append(Text(icon, fg=fg)) + return t + + def __init__(self, theme=None): + PeriodicUpdater.__init__(self) + Section.__init__(self, theme) + self.changeInterval(5) + class SshAgentProvider(PeriodicUpdater): def fetcher(self): cmd = ["ssh-add", "-l"] @@ -405,6 +469,7 @@ class GpgAgentProvider(PeriodicUpdater): self.changeInterval(5) class KeystoreProvider(Section, MergedUpdater): + # TODO OPTI+FEAT Use ColorCountsSection and not MergedUpdater, this is useless ICON = '' def __init__(self, theme=None): @@ -412,8 +477,6 @@ class KeystoreProvider(Section, MergedUpdater): Section.__init__(self, theme) class NotmuchUnreadProvider(ColorCountsSection, InotifyUpdater): - # TODO OPTI Transform InotifyUpdater (watching notmuch folder should be - # enough) COLORABLE_ICON = '' def subfetcher(self): @@ -456,7 +519,6 @@ class TodoProvider(ColorCountsSection, InotifyUpdater): COLORABLE_ICON = '' def updateCalendarList(self): - print(459) calendars = sorted(os.listdir(self.dir)) for calendar in calendars: # If the calendar wasn't in the list @@ -473,7 +535,6 @@ class TodoProvider(ColorCountsSection, InotifyUpdater): with open(path, 'r') as f: self.colors[calendar] = f.read().strip() self.calendars = calendars - print(475, self.calendars) def __init__(self, dir, theme=None): """ @@ -539,6 +600,9 @@ class I3WorkspacesProviderSection(Section): else: return self.parent.themeNormal + # TODO On mode change the state (shown / hidden) gets overriden so every + # tab is shown + def show(self): self.updateTheme(self.selectTheme()) self.updateText(self.fullName if self.focused else self.shortName) @@ -553,10 +617,15 @@ class I3WorkspacesProviderSection(Section): self.fullName = self.parent.customNames[name] \ if name in self.parent.customNames else name + def switchTo(self): + self.parent.i3.command('workspace {}'.format(self.shortName)) + + def __init__(self, name, parent): Section.__init__(self) self.parent = parent self.setName(name) + self.setDecorators(clickLeft=self.switchTo) def empty(self): self.updateTheme(self.parent.themeNormal) @@ -564,7 +633,7 @@ class I3WorkspacesProviderSection(Section): class I3WorkspacesProvider(Section, I3Updater): - # TODO Multi-screen + # TODO FEAT Multi-screen def initialPopulation(self, parent): """ @@ -674,10 +743,13 @@ class MpdProvider(Section, ThreadedUpdater): self.start() def fetcher(self): - cur = self.mpd.currentsong() + stat = self.mpd.status() + if not len(stat) or stat["state"] == "stop": + return None + cur = self.mpd.currentsong() if not len(cur): - return '' + return None infos = [] diff --git a/config/lemonbar/updaters.py b/config/lemonbar/updaters.py index f30dc03..083cae5 100755 --- a/config/lemonbar/updaters.py +++ b/config/lemonbar/updaters.py @@ -190,10 +190,16 @@ class ThreadedUpdaterThread(threading.Thread): def __init__(self, updater, *args, **kwargs): self.updater = updater threading.Thread.__init__(self, *args, **kwargs) + self.looping = True def run(self): - while True: - self.updater.loop() + try: + while self.looping: + self.updater.loop() + except BaseException as e: + log.error("Error with {}".format(self.updater)) + log.error(e, exc_info=True) + self.updater.updateText("") class ThreadedUpdater(Updater): diff --git a/config/polybar/config b/config/polybar/config index 4ad73ba..60b6c6f 100644 --- a/config/polybar/config +++ b/config/polybar/config @@ -76,7 +76,8 @@ enable-ipc = true inherit = bar/base modules-center = mpd -modules-right = cpu memory temperature mail todo vpncheck eth wlan bbswitch xbacklight volume battery shortdate +; modules-right = cpu memory temperature mail todo vpncheck eth wlan bbswitch xbacklight volume battery shortdate +modules-right = cpu memory temperature mail vpncheck eth wlan bbswitch xbacklight volume battery shortdate tray-position = right tray-padding = 2 diff --git a/config/systemd/user/default.target.wants/mbsync.timer b/config/systemd/user/default.target.wants/mbsync.timer deleted file mode 120000 index ebd66d7..0000000 --- a/config/systemd/user/default.target.wants/mbsync.timer +++ /dev/null @@ -1 +0,0 @@ -/home/geoffrey/.local/share/systemd/user/mbsync.timer \ No newline at end of file diff --git a/config/systemd/user/default.target.wants/syncthing.service b/config/systemd/user/default.target.wants/syncthing.service deleted file mode 120000 index d55cc27..0000000 --- a/config/systemd/user/default.target.wants/syncthing.service +++ /dev/null @@ -1 +0,0 @@ -/usr/lib/systemd/user/syncthing.service \ No newline at end of file diff --git a/scripts/archive b/scripts/archive index 379f30c..9eaeaa1 100755 --- a/scripts/archive +++ b/scripts/archive @@ -1,31 +1,139 @@ #!/usr/bin/env python3 -import os import argparse +import coloredlogs +import logging +import os +import sys -parser = argparse.ArgumentParser(description="Place a folder in ~/Documents in ~/Documents/Archives and symlink it") -parser.add_argument('dir', metavar='DIRECTORY', type=str, help="The directory to archive") -parser.add_argument('-d', '--dry', action='store_true') -args = parser.parse_args() +coloredlogs.install(level='DEBUG', fmt='%(levelname)s %(message)s') +log = logging.getLogger() + +# Coding conventions: +# No leading or trailing slashes. Let os.path.join do its job + +# TODO Config arparse and pass args to the functions. No globals # Finding directories -assert('HOME' in os.environ), "Home directory unknown" -docs = os.path.realpath(os.path.join(os.environ['HOME'], 'Documents')) -assert(os.path.isdir(docs)), "Documents folder not found" -arcs = os.path.join(docs, 'Archives') -assert(os.path.isdir(arcs)), "Archives folder not found" +assert 'HOME' in os.environ, "Home directory unknown" +DOCS = os.path.realpath(os.path.join(os.environ['HOME'], 'Documents')) +assert os.path.isdir(DOCS), "Documents folder not found" +ARCS = os.path.realpath(os.path.join(os.environ['HOME'], 'Archives')) +assert os.path.isdir(ARCS), "Archives folder not found" + + +def dirRange(relpath): + splits = relpath.split(os.path.sep) + res = list() + + for p in range(len(splits)): + partPath = os.path.join(*splits[:p+1]) + + arcPath = os.path.join(os.path.join(ARCS, partPath)) + docPath = os.path.join(os.path.join(DOCS, partPath)) + + res.append((docPath, arcPath)) + + return res + +def travel(relpath): + """ + Dunno what this will do, let's write code and see. + """ + wholeRange = dirRange(relpath) + for tup in wholeRange: + isLast = wholeRange[-1] == tup + docPath, arcPath = tup + linkPath = os.path.relpath(arcPath, start=docPath) + + log.debug(f"47 {tup}") + + if not os.path.exists(docPath) and not os.path.exists(arcPath): + log.error("Not existing") + sys.exit(1) + elif os.path.isdir(docPath) and os.path.isdir(arcPath) and not isLast: + log.debug("Both folder") + continue + elif os.path.isdir(docPath) and os.path.isdir(arcPath) and isLast: + log.error("This should fail for some reason, maybe") + sys.exit(1) + elif os.path.islink(docPath) and os.path.exists(arcPath): + currentLink = os.readlink(docPath) + if currentLink != linkPath: + log.warning(f"'{docPath}' is pointing to '{currentLink}' " + + f"but should point to '{linkPath}'.") + # TODO Fixing if asked for + sys.exit(1) + log.debug("Early link already exists {docPath} → {arcPath}") + return + elif not os.path.exists(docPath) and os.path.exists(arcPath): + log.debug("Only existing on archive side, linking") + print(f"ln -s {linkPath} {docPath}") + elif os.path.exists(docPath) and not os.path.exists(arcPath) \ + and isLast: + log.debug("Only existing on doc side, moving and linking") + print(f"mv {docPath} {arcPath}") + print(f"ln -s {linkPath} {docPath}") + elif os.path.exists(docPath) and not os.path.exists(arcPath) \ + and not isLast: + raise NotImplementedError("Here comes the trouble") + else: + log.error("Unhandled case") + sys.exit(1) + + +def ensureLink(relpath): + """ + Ensure that ~/Documents/$relpath points to ~/Archives/$relpath + """ + arcPath = os.path.join(os.path.join(ARCS, relpath)) + docPath = os.path.join(os.path.join(DOCS, relpath)) + assert os.path.exists(arcPath) + + # For each tree element of the path + for docPath, arcPath in dirRange(relpath): + linkPath = os.path.relpath(arcPath, start=docPath) + + def installLink(): + if args.dry: + print(f"ln -s {linkPath} {docPath}") + else: + os.symlink(linkPath, docPath) + + if os.path.islink(docPath): + currentLink = os.readlink(docPath) + if currentLink != linkPath: + log.warning(f"'{docPath}' is pointing to '{currentLink}' " + + f"but should point to '{linkPath}'. Fixing") + if args.dry: + print(f"rm {docPath}") + else: + os.unlink(docPath) + installLink() + return + elif not os.path.exists(docPath): + installLink() + return + elif os.path.isdir(docPath): + continue + else: + raise RuntimeError(f"'{docPath}' exists and is not a directory " + + f"or a link. Unable to link it to '{linkPath}'") + raise RuntimeError(f"'{docPath}' is a directory. Unable to link it to " + + f"'{linkPath}'") + def archive(docdir): docdir = os.path.realpath(args.dir) - assert(os.path.isdir(docdir)), docdir + " must be a directory" + assert os.path.isdir(docdir), docdir + " must be a directory" - assert(docdir.startswith(docs)), "Directory is not in the document folder" - assert(not docdir.startswith(arcs)), "Directory is already in the archive folder" + assert docdir.startswith(DOCS), "Directory is not in the document folder" + assert not docdir.startswith(ARCS), "Directory is already in the archive folder" - reldir = os.path.relpath(docdir, docs) + reldir = os.path.relpath(docdir, DOCS) print("ARC", reldir) - arcdir = os.path.join(arcs, reldir) + arcdir = os.path.join(ARCS, reldir) parentArcdir = os.path.realpath(os.path.join(arcdir, '..')) parentDocdir = os.path.realpath(os.path.join(docdir, '..')) linkDest = os.path.relpath(arcdir, parentDocdir) @@ -35,9 +143,9 @@ def archive(docdir): # If the directory exists if os.path.isdir(arcdir): return - # for f in os.listdir(arcdir): - # assert(os.path.isdir(f)), "Something unknown in Archive dir") - # archive(os.path.join(arcdir, f)) + # for f in os.listdir(arcdir): + # assert os.path.isdir(f), "Something unknown in Archive dir") + # archive(os.path.join(arcdir, f)) # If the directory doesn't exist, create the directories under it and move all the folder else: @@ -58,8 +166,17 @@ def archive(docdir): os.symlink(linkDest, docdir) - def unarchive(arcdir): - return + pass -archive(args.dir) + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description="Place a folder in ~/Documents in ~/Documents/Archives and symlink it") + parser.add_argument('dir', metavar='DIRECTORY', type=str, help="The directory to archive") + parser.add_argument('-d', '--dry', action='store_true') + args = parser.parse_args() + args.dry = True # DEBUG + + # archive(args.dir) + ensureLink(args.dir) diff --git a/scripts/install-arch b/scripts/install-arch index c9676c3..cf083aa 100755 --- a/scripts/install-arch +++ b/scripts/install-arch @@ -21,30 +21,18 @@ function prompt { # text } # Don't ask for things that are already there -if which pacaur &> /dev/null; then - PACAUR=1 +if which aurman &> /dev/null; then + AURMAN=1 fi -if which bauerbill &> /dev/null; then - BAUERBILL=1 +if [ -z $AURMAN ]; then + prompt "Do you want aurman on this machine?" + AURMAN=$? fi - -if [ -z $PACAUR ]; then - prompt "Do you want pacaur on this machine?" - PACAUR=$? -fi -if [ $PACAUR == 1 ]; then - if [ -z $BAUERBILL ]; then - prompt "Do you want bauerbill on this machine?" - BAUERBILL=$? - fi -else - BAUERBILL=0 -fi - # COMMON # Install packages if they aren't installed function inst { + # Could also use --needed but, meh for pkg in $*; do pacman -Q $pkg &> /dev/null if [ $? == 1 ]; then @@ -69,14 +57,13 @@ inst wget # Aur -pacman -Q pacaur &> /dev/null -if [[ $PACAUR == 1 && $? == 1 ]]; then - installPKGBUILD "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=cower" - installPKGBUILD "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=pacaur" +pacman -Q aurman &> /dev/null +if [[ $AURMAN == 1 && $? == 1 ]]; then + installPKGBUILD "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=aurman" fi # Git for /etc -sudo pacman -S etckeeper --noconfirm --needed +inst etckeeper sudo etckeeper init sudo etckeeper commit "~/.dotfiles/scripts/install-arch commit" @@ -86,25 +73,11 @@ if pacman -Q pamac &> /dev/null ; then fi # Ccache -sudo pacman -S ccache --noconfirm --needed +inst ccache sudo sed 's|BUILDENV=\(.\+\)!ccache\(.\+\)|BUILDENV=\1ccache\2|' /etc/makepkg.conf -i -# Bauerbill -pacman -Q bauerbill &> /dev/null -if [[ $BAUERBILL == 1 && $? == 1 ]]; then - sudo pacman -Sy - - gpg --recv-keys 1D1F0DC78F173680 - installPKGBUILD http://xyne.archlinux.ca/projects/reflector/pkgbuild/PKGBUILD - pacaur -S bauerbill --noconfirm --noedit - - bauerbill -Su --noconfirm -else - sudo pacman -Syu -fi - # TLP -sudo pacman -S tlp --noconfirm --needed +inst tlp sudo sed 's|SATA_LINKPWR_ON_BAT=min_power|SATA_LINKPWR_ON_BAT=max_performance|' /etc/default/tlp -i sudo systemctl enable tlp.service tlp-sleep.service sudo systemctl disable systemd-rfkill.service systemd-rfkill.socket @@ -117,7 +90,7 @@ echo -e "[Service]\nExecStartPre=/bin/sh -c 'setleds +num < /dev/%I'" | sudo sys sudo sed "s|#MAKEFLAGS=\"-j2\"|MAKEFLAGS=\"-j$(nproc)\"|" /etc/makepkg.conf -i # Time synchronisation -sudo pacman -S ntp --noconfirm --needed +inst ntp sudo systemctl start ntpd sudo systemctl enable ntpd diff --git a/scripts/install-prefs b/scripts/install-prefs index ac288ed..0cca79e 100755 --- a/scripts/install-prefs +++ b/scripts/install-prefs @@ -54,7 +54,14 @@ if which pacman &> /dev/null; then function installFileOne { # file sudo pacman -U "$1" } - if which pacaur &> /dev/null; then + if which aurman &> /dev/null; then + function altInstallOne { # package + pacman -Q $1 &> /dev/null + if [ $? == 1 ]; then + aurman -S "$1" --noconfirm --noedit + fi + } + elif which pacaur &> /dev/null; then function altInstallOne { # package pacman -Q $1 &> /dev/null if [ $? == 1 ]; then @@ -174,10 +181,11 @@ function systemdUserUnit { # Common CLI +changeColors monokai # Utils -inst coreutils man openssl-tool grep sed sh tar if [ $TERMUX == 1 ]; then + inst coreutils man openssl-tool grep sed sh tar inst termux-api if [ $ADMIN == 1 ]; then inst tsu @@ -186,17 +194,10 @@ fi inst moreutils screen ncdu lsof htop proxytunnel pv curl wget netcat mosh bash-completion rsync pwgen fzf highlight # TODO Test those who are on Debian machines and those who aren't if [ $ARCH == 1 ]; then - inst bash-completion tldr + inst bash-completion altInst gopass else inst pass - wget -qO ~/.local/bin/ https://raw.githubusercontent.com/pepa65/tldr-bash-client/master/tldr - chmod +x ~/.local/bin/tldr -fi -tldr -u -if [[ $ARCH == 1 && $ADMIN == 1 ]]; then - inst pkgfile - sudo systemctl enable pkgfile-update.timer fi # Dev @@ -207,20 +208,17 @@ elif [ $ARCH == 1 ]; then else inst make fi -inst git +inst git # Text editor -if [ $TERMUX == 1 ]; then - inst vim-python -elif [ $DEBIAN == 1 ]; then - inst vim-nox - if [ $ADMIN == 0 ]; then - debloc altern vim nox - fi -else - inst vim +inst neovim +if [ $DEBIAN == 1]; then + inst python-neovim pyhon3-neovim +elif [ $ARCH == 1]; then + inst python2-neovim python-neovim fi + if [ $DEBIAN == 1 ]; then inst exuberant-ctags else @@ -233,54 +231,13 @@ if [ $GUI == 1 ]; then .Xresources.d/configure # Desktop manager - inst i3 i3lock dunst unclutter xautolock feh numlockx scrot rxvt-unicode xclip - curl "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/a8386aae19e200ddb0f6845b5feeee5eb7013687/fonts/fontawesome-webfont.ttf" > ~/.local/share/fonts/fontawesome-webfont.ttf + inst dunst feh i3-wm i3lock numlockx qutebrowser rofi rxvt-unicode scrot trayer unclutter xautolock xclip if [ $ARCH == 1 ]; then - inst xorg-xinit - altInst polybar-git autorandr-git keynav-enhanced pacmixer - else - # Compiling polybar - if ! which polybar > /dev/null; then - inst debhelper cmake libxcb-icccm4-dev libxcb-image0-dev libxcb-randr0-dev libx11-dev libxcb1-dev libxcb-util-dev libx11-xcb-dev linux-libc-dev libboost-dev x11proto-core-dev libxcb-ewmh-dev libxft-dev libasound2-dev libiw-dev libmpdclient-dev xcb-proto python-xcbgen libxcb-xkb-dev i3-wm libcairo2-dev libxcb-xrm-dev - # TODO Figure which one are really needed - #inst libasound2 libc6 libgcc1 libiw30 libmpdclient2 libstdc++6 libx11-6 libx11-xcb1 libxcb-ewmh2 libxcb-icccm4 libxcb-randr0 libxcb-xkb1 libxcb1 libxft2 - # ↓ really needed - inst libcairo2-dev libxcb-xkb-dev libxcb-randr0-dev xcb-proto libxcb-image0-dev libxcb-icccm4-dev libxcb-ewmh-dev libxcb-util0-dev python-xcbgen - - TMP=$(mktemp -d) - git clone --branch 3.0.5 --recursive https://github.com/jaagr/polybar $TMP - mkdir $TMP/build - cd $TMP/build - cmake .. - make -j`nproc` - strip bin/polybar - mv bin/polybar ~/.local/bin/ - rm -rf $TMP - fi - fi - if [ $DEBIAN == 1 ]; then - inst suckless-tools keynav - if [ $ADMIN == 0 ]; then - debloc altern dmenu xft - fi - else - inst dmenu - fi - if [ "$(source /etc/os-release; echo $NAME)" == "Manjaro Linux" ]; then - inst menda-themes menda-circle-icon-theme xcursor-menda - fi - - # qutebrowser - if [ $DEBIAN == 1 ]; then - inst python3-lxml python-tox python3-pyqt5 python3-pyqt5.qtwebkit python3-pyqt5.qtquick python3-sip python3-jinja2 python3-pygments python3-yaml python3-pyqt5.qtsql libqt5sql5-sqlite python3-pyqt5.qtwebengine python3-pyqt5.qtopengl python3-opengl - TMP_DIR=$(mktemp -d) - $(cd $TMP_DIR; wget https://qutebrowser.org/python3-pypeg2_2.15.2-1_all.deb) - $(cd $TMP_DIR; wget https://github.com/qutebrowser/qutebrowser/releases/download/v0.11.0/qutebrowser_0.11.0-1_all.deb) - instFile $TMP_DIR/*.deb - rm -rf $TMP_DIR - - elif [ $ARCH == 1 ]; then - inst qutebrowser qt5-webengine python-opengl + inst xorg-xinit xorg-backlight + altInst lemonbar-xft-git autorandr-git keynav-enhanced pacmixer rofi-pass + elif [ $DEBIAN == 1 ]; then + # TODO autorandr pacmixer rofi-pass + inst lemonbar keynav xbacklight fi # Screen filter @@ -294,48 +251,47 @@ if [ $GUI == 1 ]; then rm $TMP fi fi - - # Graphical vim - if [ $DEBIAN == 1 ]; then - inst vim-gtk - else - inst gvim - fi fi if [ $EXTRA == 1 ]; then - # Extra dev - inst cmake clang llvm npm - inst python-rope + # Extra dev (not on mobile though ^^) + if [ $TERMUX == 0 ]; then + inst cmake clang llvm ccache python-pip + fi # Extra CLI - inst ffmpeg youtube-dl optipng syncthing ccache mutt - systemdUserUnit syncthing.service + inst ffmpeg optipng syncthing mutt jq + systemdUserUnit syncthing if [ $ARCH == 1 ]; then - inst jq - altInst pdftk translate-shell git-lfs js-beautify insect visidata-git + insta pandoc youtube-dl translate-shell + altInst insect pdftk visidata # Orga # TODO For others - inst vdirsyncer khard - altInst khal todoman offlineimap + inst vdirsyncer khard todoman offlineimap khal systemdUserUnit vdirsyncer.timer + elif [ $DEBIAN == 1]; then + inst pandoc pdftk visidata translate-shell youtube-dl else # translate-shell curl -L git.io/trans > ~/.local/bin/trans chmod +x ~/.local/bin/trans + + # TODO Others fi # Extra GUI if [ $GUI == 1 ]; then - inst vlc gimp mpd thunar noto-fonts-emoji musescore + inst vlc gimp mpd thunar musescore evince pdfpc texlive-{most,lang} if [ $ARCH == 1 ]; then - inst simplescreenrecorder - altInst pacmixer xcursor-menda-git menda-themes-git menda-maia-icon-theme vimpc-git mpc ashuffle-git + inst simplescreenrecorder mpc + altInst vimpc-git ashuffle-git ttf-emojione-color fi + # TODO Others + fi fi diff --git a/scripts/unziptree b/scripts/unziptree new file mode 100755 index 0000000..d109c0b --- /dev/null +++ b/scripts/unziptree @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import os +import subprocess + +for root, dirs, files in os.walk("."): + for name in files: + base, ext = os.path.splitext(name) + if ext.lower() != ".zip": + continue + + filepath = os.path.join(root, name) + dirpath = os.path.join(root, base) + print(filepath) + + os.mkdir(dirpath) + + cmd = ["unzip", os.path.realpath(filepath)] + r = subprocess.run(cmd, cwd=dirpath) + r.check_returncode() + + os.unlink(filepath) diff --git a/vimrc b/vimrc index d68cf57..8bd6940 100644 --- a/vimrc +++ b/vimrc @@ -58,6 +58,8 @@ Plug 'junegunn/fzf.vim' Plug 'ervandew/supertab' Plug 'dpelle/vim-LanguageTool' Plug 'terryma/vim-smooth-scroll' +Plug 'vim-pandoc/vim-pandoc' +Plug 'vim-pandoc/vim-pandoc-syntax' call plug#end() @@ -143,6 +145,10 @@ let g:SuperTabContextDefaultCompletionType = "" let g:languagetool_jar = "/usr/share/java/languagetool/languagetool-commandline.jar" +""" vim-pandoc """ +let g:pandoc#modules#disabled = ["folding"] +let g:pandoc#syntax#conceal#use = 0 + """ VIM SETTINGS """