mirror of
https://github.com/rbreaves/kinto.git
synced 2025-08-05 10:36:39 +02:00
- Added AppIndicator3 system tray app for Linux
This commit is contained in:
@@ -1,36 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Generated with glade 3.16.1 -->
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk+" version="3.10"/>
|
|
||||||
<object class="GtkMenu" id="menu1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="menuitem1">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Show pop-up</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="onNotify" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="menuitem2">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Show/hide window</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="onShowOrHide" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="menuitem3">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="label" translatable="yes">Quit</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="onQuit" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
@@ -1,102 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
# Based on
|
|
||||||
#
|
|
||||||
# gtk-example.py
|
|
||||||
# (c) Aleksander Alekseev 2016
|
|
||||||
# http://eax.me/
|
|
||||||
#
|
|
||||||
# Additions made by Ben Reaves
|
|
||||||
# 2020
|
|
||||||
# http://kinto.sh
|
|
||||||
#
|
|
||||||
|
|
||||||
import signal
|
|
||||||
import os
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('Notify', '0.7')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import Notify
|
|
||||||
|
|
||||||
APPID = "kinto"
|
|
||||||
CURRDIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
# could be PNG or SVG as well
|
|
||||||
ICON = os.path.join(CURRDIR, '../icons/kinto-invert.svg')
|
|
||||||
# ICON = Gtk.Image.new_from_icon_name("kinto-invert", Gtk.IconSize.BUTTON)
|
|
||||||
|
|
||||||
# Cross-platform tray icon implementation
|
|
||||||
# See:
|
|
||||||
# * http://ubuntuforums.org/showthread.php?t=1923373#post11902222
|
|
||||||
# * https://github.com/syncthing/syncthing-gtk/blob/master/syncthing_gtk/statusicon.py
|
|
||||||
class TrayIcon:
|
|
||||||
|
|
||||||
def __init__(self, appid, icon, menu):
|
|
||||||
self.menu = menu
|
|
||||||
|
|
||||||
APPIND_SUPPORT = 1
|
|
||||||
try:
|
|
||||||
from gi.repository import AppIndicator3
|
|
||||||
except:
|
|
||||||
APPIND_SUPPORT = 0
|
|
||||||
|
|
||||||
if APPIND_SUPPORT == 1:
|
|
||||||
self.ind = AppIndicator3.Indicator.new(
|
|
||||||
appid, icon,
|
|
||||||
AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
|
|
||||||
self.ind.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
|
|
||||||
self.ind.set_menu(self.menu)
|
|
||||||
else:
|
|
||||||
self.ind = Gtk.StatusIcon()
|
|
||||||
self.ind.set_from_file(icon)
|
|
||||||
self.ind.connect('popup-menu', self.onPopupMenu)
|
|
||||||
|
|
||||||
def onPopupMenu(self, icon, button, time):
|
|
||||||
self.menu.popup(None, None, Gtk.StatusIcon.position_menu, icon,
|
|
||||||
button, time)
|
|
||||||
|
|
||||||
|
|
||||||
class Handler:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.window_is_hidden = False
|
|
||||||
|
|
||||||
def onShowButtonClicked(self, button):
|
|
||||||
msg = "Clicked: " + entry.get_text()
|
|
||||||
dialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.INFO,
|
|
||||||
Gtk.ButtonsType.OK, msg)
|
|
||||||
dialog.run()
|
|
||||||
dialog.destroy()
|
|
||||||
|
|
||||||
def onNotify(self, *args):
|
|
||||||
Notify.Notification.new("Notification", "Hello!", ICON).show()
|
|
||||||
|
|
||||||
def onShowOrHide(self, *args):
|
|
||||||
if self.window_is_hidden:
|
|
||||||
window.show()
|
|
||||||
else:
|
|
||||||
window.hide()
|
|
||||||
|
|
||||||
self.window_is_hidden = not self.window_is_hidden
|
|
||||||
|
|
||||||
def onQuit(self, *args):
|
|
||||||
Notify.uninit()
|
|
||||||
Gtk.main_quit()
|
|
||||||
|
|
||||||
# Handle pressing Ctr+C properly, ignored by default
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
|
||||||
builder.add_from_file('kintonotify.glade')
|
|
||||||
builder.connect_signals(Handler())
|
|
||||||
|
|
||||||
# window = builder.get_object('window1')
|
|
||||||
# window.set_icon_from_file(ICON)
|
|
||||||
# window.show_all()
|
|
||||||
|
|
||||||
entry = builder.get_object('entry1')
|
|
||||||
menu = builder.get_object('menu1')
|
|
||||||
icon = TrayIcon(APPID, ICON, menu)
|
|
||||||
Notify.init(APPID)
|
|
||||||
|
|
||||||
Gtk.main()
|
|
196
xkeysnail-config/trayapps/appindicator/kintotray.py
Executable file
196
xkeysnail-config/trayapps/appindicator/kintotray.py
Executable file
@@ -0,0 +1,196 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
gi.require_version('AppIndicator3', '0.1')
|
||||||
|
gi.require_version('Notify', '0.7')
|
||||||
|
|
||||||
|
import signal,subprocess,time,os
|
||||||
|
from shutil import which
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import AppIndicator3 as appindicator
|
||||||
|
from gi.repository import Notify as notify
|
||||||
|
|
||||||
|
APPINDICATOR_ID = 'Kinto'
|
||||||
|
|
||||||
|
class Indicator():
|
||||||
|
|
||||||
|
homedir = os.path.expanduser("~")
|
||||||
|
kconfig = homedir+"/.config/kinto/kinto.py"
|
||||||
|
|
||||||
|
enable_id = 0
|
||||||
|
winmac_id = 0
|
||||||
|
chkautostart_id = 0
|
||||||
|
autostart_bool = False
|
||||||
|
menu = Gtk.Menu()
|
||||||
|
checkbox_autostart = Gtk.CheckMenuItem('Autostart')
|
||||||
|
checkbox_enable = Gtk.CheckMenuItem('Kinto Enabled')
|
||||||
|
button_config = Gtk.MenuItem('Edit Config')
|
||||||
|
# Keyboard type set below
|
||||||
|
button_syskb = Gtk.MenuItem('System Shortcuts')
|
||||||
|
button_region = Gtk.MenuItem('Change Language')
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.indicator = appindicator.Indicator.new(APPINDICATOR_ID, self.homedir+'/.config/kinto/kinto-invert.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
|
||||||
|
self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
|
||||||
|
self.indicator.set_menu(self.build_menu())
|
||||||
|
notify.init(APPINDICATOR_ID)
|
||||||
|
|
||||||
|
def build_menu(self):
|
||||||
|
|
||||||
|
with open(self.kconfig) as configfile:
|
||||||
|
autostart_line = configfile.read().split('\n')[1]
|
||||||
|
|
||||||
|
# Autostart
|
||||||
|
if "autostart = true" in autostart_line.casefold():
|
||||||
|
autostart_bool = True
|
||||||
|
|
||||||
|
if autostart_bool:
|
||||||
|
subprocess.Popen(['sudo', 'systemctl','restart','xkeysnail'])
|
||||||
|
self.checkbox_autostart.set_active(True)
|
||||||
|
self.chkautostart_id = self.checkbox_autostart.connect('activate',self.setAutostart,False)
|
||||||
|
else:
|
||||||
|
self.checkbox_autostart.set_active(False)
|
||||||
|
self.chkautostart_id = self.checkbox_autostart.connect('activate',self.setAutostart,True)
|
||||||
|
self.menu.append(self.checkbox_autostart)
|
||||||
|
|
||||||
|
# Kinto Enable
|
||||||
|
time.sleep(5)
|
||||||
|
# sudo systemctl is-active --quiet xkeysnail
|
||||||
|
res = subprocess.Popen(['sudo', 'systemctl','is-active','--quiet','xkeysnail'])
|
||||||
|
res.wait()
|
||||||
|
|
||||||
|
self.checkbox_enable.set_label("Kinto Enabled")
|
||||||
|
|
||||||
|
if res.returncode == 0:
|
||||||
|
self.checkbox_enable.set_active(True)
|
||||||
|
self.indicator.set_icon(self.homedir+'/.config/kinto/kinto-invert.svg')
|
||||||
|
self.enable_id = self.checkbox_enable.connect('activate',self.setEnable,False)
|
||||||
|
else:
|
||||||
|
self.checkbox_enable.set_active(False)
|
||||||
|
self.indicator.set_icon(self.homedir+'/.config/kinto/kinto-color.svg')
|
||||||
|
self.enable_id = self.checkbox_enable.connect('activate',self.setEnable,True)
|
||||||
|
self.menu.append(self.checkbox_enable)
|
||||||
|
|
||||||
|
# Edit Config
|
||||||
|
self.button_config.connect('activate',self.setConfig)
|
||||||
|
self.menu.append(self.button_config)
|
||||||
|
|
||||||
|
# Set Keyboard Type
|
||||||
|
command = "perl -ne 'print if /(#.*)(# Mac)\n/' ~/.config/kinto/kinto.py | wc -l"
|
||||||
|
res = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True)
|
||||||
|
res.wait()
|
||||||
|
res = res.communicate()[0]
|
||||||
|
|
||||||
|
if res:
|
||||||
|
self.button_winmac = Gtk.MenuItem('Set Win/Mac KB Type')
|
||||||
|
self.winmac_id = self.button_winmac.connect('activate',self.setKB,"winmac")
|
||||||
|
else:
|
||||||
|
self.button_winmac = Gtk.MenuItem('Set Mac Only KB Type')
|
||||||
|
self.winmac_id = button_winmac.connect('activate',self.setKB,"mac")
|
||||||
|
self.menu.append(self.button_winmac)
|
||||||
|
|
||||||
|
# Set System Keyboard Shortcuts
|
||||||
|
self.button_syskb.connect('activate',self.setSysKB)
|
||||||
|
self.menu.append(self.button_syskb)
|
||||||
|
|
||||||
|
# Set Language
|
||||||
|
self.button_region.connect('activate',self.setRegion)
|
||||||
|
self.menu.append(self.button_region)
|
||||||
|
|
||||||
|
# item_quit = Gtk.MenuItem('Quit')
|
||||||
|
# item_quit.connect('activate', quit)
|
||||||
|
# menu.append(item_quit)
|
||||||
|
self.menu.show_all()
|
||||||
|
|
||||||
|
return self.menu
|
||||||
|
|
||||||
|
def setEnable(self,button,enableKinto):
|
||||||
|
try:
|
||||||
|
if enableKinto:
|
||||||
|
subprocess.Popen(['sudo', 'systemctl','restart','xkeysnail'])
|
||||||
|
self.checkbox_enable.set_active(True)
|
||||||
|
self.checkbox_enable.disconnect(self.enable_id)
|
||||||
|
self.enable_id = self.checkbox_enable.connect('activate',self.setEnable,False)
|
||||||
|
self.indicator.set_icon(self.homedir+'/.config/kinto/kinto-invert.svg')
|
||||||
|
|
||||||
|
else:
|
||||||
|
subprocess.Popen(['sudo', 'systemctl','stop','xkeysnail'])
|
||||||
|
self.checkbox_enable.set_active(False)
|
||||||
|
self.checkbox_enable.disconnect(self.enable_id)
|
||||||
|
self.enable_id = self.checkbox_enable.connect('activate',self.setEnable,True)
|
||||||
|
self.indicator.set_icon(self.homedir+'/.config/kinto/kinto-color.svg')
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
subprocess.Popen(['notify-send','Kinto: Error enabling!','-i','budgie-desktop-symbolic'])
|
||||||
|
|
||||||
|
def setAutostart(self,button,autostart):
|
||||||
|
try:
|
||||||
|
if autostart == False:
|
||||||
|
subprocess.Popen(['perl','-pi','-e','s/autostart = true/autostart = false/g',self.homedir+'/.config/kinto/kinto.py'])
|
||||||
|
self.checkbox_autostart.set_active(False)
|
||||||
|
self.checkbox_autostart.disconnect(self.chkautostart_id)
|
||||||
|
self.chkautostart_id = self.checkbox_autostart.connect('activate',self.setAutostart,True)
|
||||||
|
else:
|
||||||
|
subprocess.Popen(['perl','-pi','-e','s/autostart = false/autostart = true/g',self.homedir+'/.config/kinto/kinto.py'])
|
||||||
|
self.checkbox_autostart.set_active(True)
|
||||||
|
self.checkbox_autostart.disconnect(self.chkautostart_id)
|
||||||
|
self.chkautostart_id = self.checkbox_autostart.connect('activate',self.setAutostart,False)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
subprocess.Popen(['notify-send','Kinto: Error setting autostart!','-i','budgie-desktop-symbolic'])
|
||||||
|
|
||||||
|
def setConfig(self,button):
|
||||||
|
try:
|
||||||
|
if os.path.exists('/opt/sublime_text/sublime_text'):
|
||||||
|
subprocess.Popen(['/opt/sublime_text/sublime_text',self.homedir+'/.config/kinto/kinto.py'])
|
||||||
|
elif which(gedit) is not None:
|
||||||
|
subprocess.Popen(['gedit',self.homedir+'/.config/kinto/kinto.py'])
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError: # Notify user about error on running restart commands.
|
||||||
|
subprocess.Popen(['notify-send','Kinto: Error could not open config file!','-i','budgie-desktop-symbolic'])
|
||||||
|
|
||||||
|
def setKB(self,button,kbtype):
|
||||||
|
try:
|
||||||
|
if kbtype == "winmac":
|
||||||
|
label = "Set Mac KB Type"
|
||||||
|
connect = "mac"
|
||||||
|
|
||||||
|
setwinmac = ['s/^(\s{3})(\s{1}#)(.*# WinMac\n|.*# WinMac -)|^(?!\s{4}#)(\s{3})(\s{1})(.*)( # )(Mac\n|Mac -)/ $3$7$6$7$8/g']
|
||||||
|
|
||||||
|
else:
|
||||||
|
label = "Set Win/Mac KB Type"
|
||||||
|
connect = "winmac"
|
||||||
|
|
||||||
|
setwinmac = ['s/^(\s{3})(\s{1}#)(.*# Mac\n|.*# Mac -)|^(?!\s{4}#)(\s{3})(\s{1})(.*)( # )(WinMac)/ $3$7$6$7$8/g']
|
||||||
|
|
||||||
|
restart = ['sudo', 'systemctl','restart','xkeysnail']
|
||||||
|
cmds = ['perl','-pi','-e']+setwinmac+[self.kconfig]
|
||||||
|
|
||||||
|
subprocess.Popen(cmds)
|
||||||
|
|
||||||
|
cmdsTerm = subprocess.Popen(cmds)
|
||||||
|
cmdsTerm.wait()
|
||||||
|
|
||||||
|
subprocess.Popen(restart)
|
||||||
|
|
||||||
|
self.button_winmac.set_label(label)
|
||||||
|
self.button_winmac.disconnect(self.winmac_id)
|
||||||
|
self.winmac_id = self.button_winmac.connect('activate',self.setKB,connect)
|
||||||
|
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
subprocess.Popen(['notify-send','Kinto: Error Resetting KB Type!','-i','budgie-desktop-symbolic'])
|
||||||
|
|
||||||
|
def setSysKB(self,button):
|
||||||
|
subprocess.Popen(['gnome-control-center','keyboard'])
|
||||||
|
|
||||||
|
def setRegion(self,button):
|
||||||
|
subprocess.Popen(['gnome-control-center','region'])
|
||||||
|
|
||||||
|
def quit(source):
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
Indicator()
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
Gtk.main()
|
@@ -277,7 +277,10 @@ if [[ $1 == "1" || $1 == "2" || $1 == "3" || $1 == "winmac" || $1 == "mac" || $1
|
|||||||
sudo chmod go-w+rx /usr/local/bin/logoff.sh
|
sudo chmod go-w+rx /usr/local/bin/logoff.sh
|
||||||
yes | cp -rf ./xkeysnail-config/kinto.py ./xkeysnail-config/kinto.py.new
|
yes | cp -rf ./xkeysnail-config/kinto.py ./xkeysnail-config/kinto.py.new
|
||||||
yes | cp -rf ./xkeysnail-config/limitedadmins ./xkeysnail-config/limitedadmins.new
|
yes | cp -rf ./xkeysnail-config/limitedadmins ./xkeysnail-config/limitedadmins.new
|
||||||
# yes | cp -rf ./xkeysnail-config/prexk.sh ~/.config/kinto/prexk.sh
|
yes | cp -rf ./xkeysnail-config/trayapps/appindicator/kintotray.py ~/.config/kinto/kintotray.py
|
||||||
|
yes | cp -rf ./xkeysnail-config/trayapps/BudgieApplet/icons/kinto-color-16.svg ~/.config/kinto/kinto-color.svg
|
||||||
|
yes | cp -rf ./xkeysnail-config/trayapps/BudgieApplet/icons/kinto-invert-16.svg ~/.config/kinto/kinto-invert.svg
|
||||||
|
yes | cp -rf ./xkeysnail-config/trayapps/BudgieApplet/icons/kinto-solid-16.svg ~/.config/kinto/kinto-solid.svg
|
||||||
# yes | cp -rf ./system-config/caret_status_xkey.sh ~/.config/kinto/caret_status_xkey.sh
|
# yes | cp -rf ./system-config/caret_status_xkey.sh ~/.config/kinto/caret_status_xkey.sh
|
||||||
yes | cp -rf ./xkeysnail-config/xkeysnail.service ./xkeysnail-config/xkeysnail.service.new
|
yes | cp -rf ./xkeysnail-config/xkeysnail.service ./xkeysnail-config/xkeysnail.service.new
|
||||||
# yes | cp -rf ./xkeysnail-config/xkeysnail.timer ~/.config/systemd/user/xkeysnail.timer
|
# yes | cp -rf ./xkeysnail-config/xkeysnail.timer ~/.config/systemd/user/xkeysnail.timer
|
||||||
@@ -296,6 +299,7 @@ if [[ $1 == "1" || $1 == "2" || $1 == "3" || $1 == "winmac" || $1 == "mac" || $1
|
|||||||
sudo chown root:root ./xkeysnail-config/limitedadmins.new
|
sudo chown root:root ./xkeysnail-config/limitedadmins.new
|
||||||
sudo mv ./xkeysnail-config/limitedadmins.new /etc/sudoers.d/limitedadmins
|
sudo mv ./xkeysnail-config/limitedadmins.new /etc/sudoers.d/limitedadmins
|
||||||
sed -i "s#{systemctl}#`\\which systemctl`#g" ~/.config/autostart/xkeysnail.desktop
|
sed -i "s#{systemctl}#`\\which systemctl`#g" ~/.config/autostart/xkeysnail.desktop
|
||||||
|
sed -i "s#-c \"grep#-c \"python3 {homedir}/.config/kinto/kintotray.py;grep#g" ~/.config/autostart/xkeysnail.desktop
|
||||||
sed -i "s#{xhost}#`\\which xhost`#g" ~/.config/autostart/xkeysnail.desktop
|
sed -i "s#{xhost}#`\\which xhost`#g" ~/.config/autostart/xkeysnail.desktop
|
||||||
sed -i "s#{homedir}#`echo "$HOME"`#g" ~/.config/autostart/xkeysnail.desktop
|
sed -i "s#{homedir}#`echo "$HOME"`#g" ~/.config/autostart/xkeysnail.desktop
|
||||||
# sed -i "s#{homedir}#`echo "$HOME"`#g" ~/.config/kinto/prexk.sh
|
# sed -i "s#{homedir}#`echo "$HOME"`#g" ~/.config/kinto/prexk.sh
|
||||||
@@ -377,6 +381,7 @@ if [[ $1 == "1" || $1 == "2" || $1 == "3" || $1 == "winmac" || $1 == "mac" || $1
|
|||||||
# sudo systemctl enable xkeysnail.service
|
# sudo systemctl enable xkeysnail.service
|
||||||
# fi
|
# fi
|
||||||
sudo systemctl restart xkeysnail
|
sudo systemctl restart xkeysnail
|
||||||
|
nohup python3 ~/.config/kinto/kintotray.py& >/dev/null 2>&1
|
||||||
|
|
||||||
echo -e "Adding xhost fix...\n"
|
echo -e "Adding xhost fix...\n"
|
||||||
|
|
||||||
@@ -419,12 +424,13 @@ if [[ $1 == "1" || $1 == "2" || $1 == "3" || $1 == "winmac" || $1 == "mac" || $1
|
|||||||
echo -e "\e[1m\e[32mEnabled\e[0m mutli-language support."
|
echo -e "\e[1m\e[32mEnabled\e[0m mutli-language support."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
trayApp
|
# trayApp
|
||||||
|
|
||||||
elif [[ $1 == "5" || $1 == "uninstall" || $1 == "Uninstall" ]]; then
|
elif [[ $1 == "5" || $1 == "uninstall" || $1 == "Uninstall" ]]; then
|
||||||
echo "Uninstalling Kinto - xkeysnail (udev)"
|
echo "Uninstalling Kinto - xkeysnail (udev)"
|
||||||
uninstall
|
uninstall
|
||||||
removeAppleKB
|
removeAppleKB
|
||||||
|
pkill -f kintotray
|
||||||
sudo systemctl stop xkeysnail
|
sudo systemctl stop xkeysnail
|
||||||
sudo systemctl disable xkeysnail
|
sudo systemctl disable xkeysnail
|
||||||
sudo rm /etc/sudoers.d/limitedadmins
|
sudo rm /etc/sudoers.d/limitedadmins
|
||||||
|
Reference in New Issue
Block a user