#!/usr/bin/python3
import getpass
import sys
import os,shutil
from PySide2.QtWidgets import QApplication, QLabel, QWidget, QPushButton,QVBoxLayout,QShortcut,\
QStackedWidget,QGridLayout,QTabBar,QTabWidget,QHBoxLayout,QFormLayout,QLineEdit,QComboBox,\
QStatusBar,QFileDialog,QDialog,QScrollBar,QScrollArea,QCheckBox,QTableWidget,\
QTableWidgetItem,QHeaderView,QTableWidgetSelectionRange,QInputDialog,QDesktopWidget
from PySide2 import QtGui
from PySide2.QtCore import QSize,Slot,Qt, QPropertyAnimation,QThread,QRect,QTimer,Signal,QSignalMapper,QProcess,QEvent
from edupals.ui import QAnimatedStatusBar
import gettext
import subprocess
import signal
import psutil
from passlib.hash import pbkdf2_sha256 as hashpwd
import time
import tempfile
from urllib.request import urlretrieve
from libAppRun import appRun
from appconfig.appConfigStack import appConfigStack as confStack
from app2menu import App2Menu as app2menu
QString=type("")
QInt=type(0)
TAB_BTN_SIZE=96
BTN_SIZE=128
gettext.textdomain('runomatic')
_ = gettext.gettext
class firefoxProfiles():
def __init__(self):
self.tmpDir="/tmp/.runomatic/firefox"
self.iniProfile=os.path.join(os.environ.get('HOME'),".mozilla/firefox/")
self.iniProfileSnap=os.path.join(os.environ.get('HOME'),"snap/firefox/common/.mozilla/firefox/")
self.destIniProfile=os.path.join(self.tmpDir,os.environ.get('USER'),'firefox')
#def __init__
def _debug(self,msg):
print("{}".format(str(msg)))
def iniProfilesCopy(self):
ffProfile=os.path.join(self.destIniProfile,"firefox")
snapProfile=os.path.join(self.destIniProfile,"snap")
if os.path.isdir(self.destIniProfile)==False:
os.makedirs(self.destIniProfile)
if (os.path.isdir(ffProfile)==False) and (os.path.isdir(self.iniProfile)==True):
shutil.copytree(self.iniProfile,ffProfile,ignore_dangling_symlinks=True)
if (os.path.isdir(snapProfile)==False) and (os.path.isdir(self.iniProfileSnap)==True):
shutil.copytree(self.iniProfileSnap,snapProfile,ignore_dangling_symlinks=True)
#def iniProfilesCopy
def restoreProfiles(self):
ffProfile=os.path.join(self.destIniProfile,"firefox")
snapProfile=os.path.join(self.destIniProfile,"snap")
self._debug("Restore ff profile")
self._debug("From {} -> {}".format(ffProfile,self.iniProfile))
if os.path.isdir(ffProfile):
if os.path.isdir(self.iniProfile):
shutil.rmtree(self.iniProfile)
shutil.copytree(ffProfile,self.iniProfile)
self._debug("Restore ff snap profile")
self._debug("From {} -> {}".format(snapProfile,self.iniProfileSnap))
if os.path.isdir(snapProfile):
if os.path.isdir(self.iniProfileSnap):
shutil.rmtree(self.iniProfileSnap)
shutil.copytree(snapProfile,self.iniProfileSnap)
#def restoreProfiles
#class firefoxProfiles
class QCheckBoxWithDescriptions(QCheckBox):
def __init__(self,text="",desktops=[],parent=None):
super (QCheckBoxWithDescriptions,self).__init__("",parent)
self.app2menu=app2menu.app2menu()
if text:
self.setText(text)
self.desktops=desktops
self._generateApplist(text)
def _generateApplist(self,text=''):
if self.text()!='':
text=self.text()
if text=='':
self.setToolTip(_("Empty"))
self.setEnabled(False)
return
addedApp=[]
tooltext=''
#applist=self.app2menu.get_apps_from_category(text)
#for key,item in applist.items():
# if item.get('name','') not in addedApp:
# tooltext="{0}{1}\n".format(tooltext,item.get('name'),key)
# addedApp.append(item.get('name'))
for desk in self.desktops:
if desk not in addedApp:
tooltext="{0}{1}\n".format(tooltext,desk)
addedApp.append(desk)
if tooltext=='':
tooltext=_('Empty')
self.setEnabled(False)
self.setToolTip(tooltext)
#class QCheckBoxWithDescriptions
class appZone(QWidget):
def __init__(self,parent):
super (appZone,self).__init__(parent)
self.setObjectName("appzone")
self.wid=0
def createZone(self,wid):
zone=QtGui.QWindow()
zone.show()
self.wid=wid
try:
subZone=QtGui.QWindow.fromWinId(int(wid))
except:
pass
zone=self.createWindowContainer(subZone,self,Qt.FramelessWindowHint)
zone.show()
zone.setParent(self)
zone.setFocusPolicy(Qt.NoFocus)
return(zone,self)
#def createZone
#class appZone
class navButton(QPushButton):
keypress=Signal("PyObject")
focusIn=Signal("PyObject")
def __init__(self,parent):
super (navButton,self).__init__("",parent)
self.keymap={}
self.index=1
a=QGridLayout()
self.statusBar=QAnimatedStatusBar.QAnimatedStatusBar()
self.statusBar.btn_close.hide()
self.statusBar.setStateCss("error","border-radius:25px;background-color:qlineargradient(x1:110 y1:110,x2:0 y2:1,stop:0 rgba(0,110,110,0.2), stop:1 rgba(110,110,0,0.2));color:red;text-align:center;border:0px;font-size:36px;height:256px")
self.cssError=("border-radius:25px;margin:0px;border:0px;text-decoration:none;text-align:center;background-image: url(../rsrc/blocked.png);background-position:center;background-repeat:no-repeat;color:white;font-size:%spx;min-width:%s;min-height:%s"%(BTN_SIZE,BTN_SIZE,BTN_SIZE-40))
self.cssRun=("border-radius:25px;margin:0px;border:0px;text-decoration:none;text-align:center;background-color:rgba(255,0,0,0.6);color:white;font-size:%spx;min-height:%s"%(BTN_SIZE,BTN_SIZE-40))
a.addWidget(self.statusBar,0,0,1,1)
self.setLayout(a)
for key,value in vars(Qt).items():
if isinstance(value, Qt.Key):
self.keymap[value]=key.partition('_')[2]
self.modmap={
Qt.ControlModifier: self.keymap[Qt.Key_Control],
Qt.AltModifier: self.keymap[Qt.Key_Alt],
Qt.ShiftModifier: self.keymap[Qt.Key_Shift],
Qt.MetaModifier: self.keymap[Qt.Key_Meta],
Qt.GroupSwitchModifier: self.keymap[Qt.Key_AltGr],
Qt.KeypadModifier: self.keymap[Qt.Key_NumLock]
}
self.setObjectName("PushButton")
#def __init__
def enterEvent(self,event):
self.statusBar.height_=self.height()
if self.isEnabled():
self.setFocus()
self.focusIn.emit(self)
self.resize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
self.setIconSize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
#def enterEvent
def leaveEvent(self,event):
if self.isEnabled():
self.resize(QSize(BTN_SIZE,BTN_SIZE))
self.setIconSize(QSize(BTN_SIZE,BTN_SIZE))
#def leaveEvent
def keyPressEvent(self,event):
sw_mod=False
keypressed=[]
if (event.type()==QEvent.KeyPress):
for modifier,text in self.modmap.items():
if event.modifiers() & modifier:
sw_mod=True
keypressed.append(text)
key=self.keymap.get(event.key(),event.text())
if key not in keypressed:
if sw_mod==True:
sw_mod=False
keypressed.append(key)
if sw_mod==False:
key=("+".join(keypressed))
if key not in ("Alt","Control","Super_L"):
self.keypress.emit(key)
else:
#Alt key is passed to parent. Parent then grabs the keyboard to prevent window switching
event.setAccepted(False)
#Win key is ignored
if key=="Super_L":
event.ignore()
#def keyPressEvent
#class navButton
class runomatic(QWidget):
update_signal=Signal("PyObject")
def __init__(self):
super().__init__()
self._plasmaMetaHotkey(enable=False,reconfigure=True)
self.dbg=False
exePath=sys.argv[0]
if os.path.islink(sys.argv[0]):
exePath=os.path.realpath(sys.argv[0])
self.baseDir=os.path.abspath(os.path.dirname(exePath))
signal.signal(signal.SIGUSR1,self._end_process)
signal.signal(signal.SIGUSR2,self._fail_process)
self.runoapps="/usr/share/runomatic/applications"
self.userRunoapps="{}/.config/runomatic/applications".format(os.environ.get('HOME'))
oldUser="{}/.config/runomatic.conf".format(os.environ.get('HOME'))
if os.path.isfile(oldUser):
newUser="{}/.config/runomatic/runomatic.conf".format(os.environ.get('HOME'))
if os.path.isdir(os.path.dirname(newUser))==False:
os.makedirs(os.path.dirname(newUser))
if os.path.isfile(newUser)==False:
shutil.move(oldUser,newUser)
self.launched=[]
self.blocked=[]
self.procMon=[]
self.categories={}
self.desktops={}
self.pid=0
self.app_icons={}
self.tab_icons={}
self.tab_id={}
self.focusWidgets=[]
self.appsWidgets=[]
self.launchErr=False
self.id=0
self.firstLaunch=True
self.currentTab=0
self.currentBtn=0
self.closeKey=False
self.confKey=False
self.keymap={}
self.display=os.environ['DISPLAY']
self.cache="%s/.cache/runomatic/"%os.environ['HOME']
self.defaultBg="/usr/share/runomatic/rsrc/background2.png"
self.username=getpass.getuser()
self.runner=appRun()
self._set_keymapping()
self._read_config()
self.app2menu=app2menu.app2menu()
self._render_gui()
self.oldKey=""
self.currentKey=""
self.firefoxProfiles=firefoxProfiles()
self.firefoxProfiles.iniProfilesCopy()
#def init
def _plasmaMetaHotkey(self,enable=None,reconfigure=True):
env=os.environ.copy()
cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta --delete'
if enable==False:
cmd='kwriteconfig5 --file ~/.config/kwinrc --group ModifierOnlyShortcuts --key Meta ""'
try:
a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env)
a.wait()
except Exception as e:
self._debug("kwriteconfig: %s"%e)
if reconfigure==True:
try:
cmd='qdbus org.kde.KWin /KWin reconfigure'
a=subprocess.Popen(cmd,stdin=None,stdout=None,stderr=None,shell=True,env=env)
a.wait()
except Exception as e:
self._debug("qdbus: %s"%e)
def _fail_process(self,*args):
self.launchErr=True
self._debug("PROCESS %s FAILED TO LAUNCH"%args[0])
#def _fail_process
def _end_process(self,*args):
for thread in self.runner.getDeadProcesses():
idx=self._get_tabId_from_thread(thread)
if idx and idx>0:
if self.launchErr:
self.blocked.append(self.tab_id[idx]['app'])
self._on_tabRemove(idx)
self.focusWidgets[self.currentBtn].index=None
self.focusWidgets[self.currentBtn].statusBar.setText("")
if self.launchErr:
self.launchErr=False
self.focusWidgets[self.currentBtn].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssError)
self.focusWidgets[self.currentBtn].statusBar.show(state='error')
# self._on_tabSelect(0)
self.focusWidgets[self.currentBtn].setFocus()
#def _end_process
def _debug(self,msg):
if self.dbg:
print("runomatic: %s"%msg)
#def _debug
def _set_keymapping(self):
#Disable meta association within plasma. I've no eggs to capture and block this event in kde
for key,value in vars(Qt).items():
if isinstance(value, Qt.Key):
self.keymap[value]=key.partition('_')[2]
self.modmap={
Qt.ControlModifier: self.keymap[Qt.Key_Control],
Qt.AltModifier: self.keymap[Qt.Key_Alt],
Qt.ShiftModifier: self.keymap[Qt.Key_Shift],
Qt.MetaModifier: self.keymap[Qt.Key_Meta],
Qt.GroupSwitchModifier: self.keymap[Qt.Key_AltGr],
Qt.KeypadModifier: self.keymap[Qt.Key_NumLock],
Qt.Key_Super_L: self.keymap[Qt.Key_Meta],
Qt.Key_Super_R: self.keymap[Qt.Key_Meta]
}
self.sigmap_tabSelect=QSignalMapper(self)
self.sigmap_tabSelect.mapped[QInt].connect(self._on_tabSelect)
self.sigmap_tabRemove=QSignalMapper(self)
self.sigmap_tabRemove.mapped[QInt].connect(self._on_tabRemove)
#Shortcut for super_keys
#def _set_keymapping
def _read_config(self):
data=self.runner.get_apps()
self.categories=data.get('categories')
self.desktops=data.get('desktops')
self.keybinds=data.get('keybinds',{})
self.password=data.get('password')
self.close_on_exit=data.get('close',False)
self.bg=data.get('background',self.defaultBg)
if os.path.isfile(self.bg)==False:
self.bg=self.defaultBg
self.runner.setBg(self.bg)
#def _read_config(self):
def _init_gui(self):
self.setWindowFlags(Qt.FramelessWindowHint)
self.setWindowFlags(Qt.X11BypassWindowManagerHint)
self.setWindowState(Qt.WindowFullScreen)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.setWindowModality(Qt.WindowModal)
if os.path.isfile(self.bg)==False:
self.bg="/usr/share/runomatic/rsrc/background2.png"
self.previousIcon=QtGui.QIcon.fromTheme("go-previous")
self.previousIcon=QtGui.QIcon.fromTheme("go-home")
btnPrevious=QPushButton()
btnPrevious.setObjectName("PushButton")
btnPrevious.setIcon(self.previousIcon)
btnPrevious.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
self.sigmap_tabSelect.setMapping(btnPrevious,0)
btnPrevious.clicked.connect(self.sigmap_tabSelect.map)
self.homeIcon=QtGui.QIcon.fromTheme("go-home")
btnHome=QPushButton()
btnHome.setObjectName("PushButton")
btnHome.setIcon(self.homeIcon)
btnHome.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
self.tab_id[0]={'index':self.id,'thread':0,'xephyr':None,'wid':0,'show':btnHome,'close':btnPrevious,'display':"%s"%os.environ['DISPLAY']}
self.closeIcon=QtGui.QIcon.fromTheme("window-close")
self.grab=False
self.setStyleSheet(self._define_css())
#monitor=QDesktopWidget().screenGeometry(1)
screenGeometry=app.screens()[0].geometry()
self.move(screenGeometry.left(),screenGeometry.top())
cursor=QtGui.QCursor(Qt.PointingHandCursor)
self.setCursor(cursor)
self.showFullScreen()
#self.show()
#def _init_gui(self):
def _render_gui(self):
#Enable transparent window
# self.setStyleSheet('background:transparent')
# self.setAttribute(Qt.WA_TranslucentBackground)
####
self._init_gui()
self.box=QGridLayout()
self.statusBar=QAnimatedStatusBar.QAnimatedStatusBar()
self.statusBar.setStateCss("error","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,255,0,1), stop:1 rgba(255,0,0,0.6));text-decoration:none;color:white;text-align:center;font-size:128px;height:256px")
self.statusBar.setStateCss("error2","background-color:qlineargradient(x1:0 y1:0,x2:0 y2:1,stop:0 rgba(255,0,0,1), stop:1 rgba(255,0,0,0.6));color:white;text-align:center;text-decoration:none;height:20px")
self.statusBar.height_=152
self.box.addWidget(self.statusBar,0,0,1,2)
self.tabBar=self._tabBar()
self.setObjectName("tabbar")
# self.tabBar.setAttribute(Qt.WA_TranslucentBackground)
self.tabBar.currentChanged.connect(lambda:self._on_tabChanged(False))
self.box.addWidget(self.tabBar,0,0,1,1)
self.setLayout(self.box)
self.tabBar.setFocusPolicy(Qt.NoFocus)
if self.focusWidgets:
self._set_focus("")
else:
wdg=QWidget()
lyt=QGridLayout()
wdg.setLayout(lyt)
wdg.setObjectName("container")
wdg.setStyleSheet("#container {background-color:white;qproperty-alignment:AlignCenter;margin:3px;border:3px solid white}")
msg=_("There's no launchers to show.\nDid you run the configure app?")
lbl=QLabel("{}".format(msg))
lyt.addWidget(lbl,0,0,1,4)
btn=QPushButton(_("Launch configuration app"))
btn.setStyleSheet("")
btn.clicked.connect(lambda:self._launchConf(None))
lyt.addWidget(btn,1,0,1,4)
lbl2=QLabel(_("Or you can set directly a template from the menu"))
lyt.addWidget(lbl2,2,1,1,2)
catList=self.app2menu.get_categories_tree()
row=3
col=0
blacklist=['information','translation','internet','settingsmenu','system','utilities','lliurex preferences','lliurex administration']
blacklist=[]
for cat,desktops in catList.items():
if cat and cat.lower() not in blacklist:
chk=QCheckBoxWithDescriptions(text=cat,desktops=desktops)
lyt.addWidget(chk,row,col,1,1)
chk.stateChanged.connect(lambda:btnTemplates.setEnabled(True))
col+=1
if col==4:
row+=1
col=0
btnTemplates=QPushButton(_("Set apps from selected templates"))
btnTemplates.clicked.connect(self._applyTemplates)
btnTemplates.setEnabled(False)
lyt.addWidget(btnTemplates,row+1,0,1,4)
lbl3=QLabel(_("Remember that run-o-matic will block the desktop. The only way for close run-o-matic is with Alt+F4"))
lyt.addWidget(lbl3,row+2,0,1,4)
self.box.addWidget(wdg,0,0,1,1,Qt.AlignCenter)
#def _render_gui
def _launchConf(self,nolaunch=False):
if isinstance(nolaunch,bool):
if nolaunch==True:
return
if os.path.isfile("%s/runoconfig.py"%self.baseDir):
if nolaunch==None:
self.close()
cmd=["{}/runoconfig.py".format(self.baseDir)]
try:
subprocess.run(cmd)
except Exception as e:
print(_("{0}: {1}".format(msgErr,e)))
msgErr=_("Error launching config")
os.execv("%s/runomatic.py"%self.baseDir,("1","1"))
else:
self.showMessage(_("runoconfig not found at %s"%self.baseDir),"error2",20)
#def launchConf
def _applyTemplates(self):
categories=[]
items =(self.box.itemAt(item).widget() for item in range(self.box.count()))
userRunoapps="{}/.config/runomatic/applications".format(os.environ['HOME'])
if os.path.isdir(userRunoapps)==False:
os.makedirs(userRunoapps)
for item in items:
if isinstance(item,QWidget):
for chk in item.findChildren(QCheckBoxWithDescriptions):
if chk.isChecked():
self._debug("Loading apps from {}".format(chk.text()))
categories.append(chk.text())
dlg=QDialog()
lay=QGridLayout()
dlg.setLayout(lay)
msg=_("The next categories will be added")
lbl=QLabel("{0}:".format(msg))
lay.addWidget(lbl,0,0,1,3)
lbl2=QLabel("{0}
".format("
".join(categories)))
lbl2.setStyleSheet("background-color:white;color:black;margin:3px;border:3px solid white")
lay.addWidget(lbl2,1,0,1,3)
msg=_("On apply Run-O-Matic will be launched with the apps from choosed categories.
For fine tuning it's recommended to launch Run-O-Config.")
lbl3=QLabel("{0}".format(msg))
lay.addWidget(lbl3,2,0,1,3)
btnOk=QPushButton(_("Apply"))
btnOk.clicked.connect(lambda:self._setTemplates(categories))
lay.addWidget(btnOk,3,0,1,1)
btnCancel=QPushButton(_("Cancel"))
btnCancel.clicked.connect(dlg.close)
lay.addWidget(btnCancel,3,1,1,2,Qt.AlignRight)
#btnConfig=QPushButton(_("Launch Run-O-Config"))
#btnConfig.clicked.connect(lambda:self._launchConf(True))
#lay.addWidget(btnConfig,3,2,1,1)
dlg.exec_()
def _setTemplates(self,categories):
self.runner.write_config(categories,key='categories')
os.execv("%s/runomatic.py"%self.baseDir,["1","2"])
# def _setTemplatesAndLaunch(self,categories):
# self.runner.write_config(categories,key='categories')
# print("PASO")
# os.execv("%s/runoconfig.py"%self.baseDir,["1","2"])
def closeEvent(self,event):
if self.password:
text=_("Insert the password")
if self.close_on_exit==True:
text=_("Insert the password. Current session will also be closed.")
pwd,resp=QInputDialog.getText(self,_("Confirm close"),text,QLineEdit.Password)
if resp:
if not hashpwd.verify(pwd,self.password):
event.ignore()
else:
event.ignore()
self._plasmaMetaHotkey(enable=True,reconfigure=True)
for index in self.tab_id.keys():
if index:
th=self.tab_id[index].get('thread',None)
if th:
self.runner.send_signal_to_thread("kill",th)
xe=self.tab_id[index].get('xephyr',None)
if xe:
self.runner.send_signal_to_thread("kill",xe)
dsp=self.tab_id[index].get('display',None)
if dsp:
self.runner.stop_display(self.tab_id[index].get('wid',''),dsp)
xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":",""))
if os.path.isfile(xlockFile):
os.remove(xlockFile)
os.environ['DISPLAY']=":0"
self.firefoxProfiles.restoreProfiles()
#Clean files
if os.path.isdir("/tmp/.runomatic"):
try:
shutil.rmtree("/tmp/.runomatic")
except:
pass
if os.path.isfile("/tmp/.runovncstartup"):
try:
os.remove("/tmp/.runovncstartup")
except Exception as e:
print(e)
pass
if str(self.close_on_exit).lower()=='true':
confKey=''
if self.keybinds:
confKey=self.keybinds.get('conf','')
if len(confKey.strip())>0:
print("Closing session...")
subprocess.run(["loginctl","terminate-user","%s"%self.username])
#def closeEvent
def keyPressEvent(self,event):
self.oldKey=self.currentKey
key=self.keymap.get(event.key(),event.text())
self.currentKey=key
if key in ("Alt" ,"Super_L"):
self.grab=True
self.grabKeyboard()
#def eventFilter
def keyReleaseEvent(self,event):
key=self.keymap.get(event.key(),event.text())
confKey=''
if self.keybinds:
confKey=self.keybinds.get('conf',None)
if key not in ('Tab','Super_L'):
sw=True
if key=='F4' and self.grab:
self.closeKey=True
elif key==confKey or "{}+{}".format(self.oldKey,key)==confKey:
if os.path.isfile("%s/runoconfig.py"%self.baseDir):
sw=self.close_on_exit
self.close_on_exit=False
if self.close():
print("Launch conf")
self._launchConf()
#os.execv("%s/runoconfig.py"%self.baseDir)
self.close_on_exit=sw
else:
event.ignore()
self.showMessage(_("runoconfig not found"),"error2",20)
elif key.isdigit():
if int(key)<=self.tabBar.count():
self._on_tabSelect(int(key),True)
cont=0
for btn in self.focusWidgets:
if btn.statusBar.label.text()==key:
self.currentBtn=cont
self.focusWidgets[cont].setFocus()
cont+=1
else:
event.ignore()
else:
sw=False
if sw:
self.releaseKeyboard()
if key in ('Alt','Control','Super_L'):
if key!='Super_L':
self.releaseKeyboard()
else:
event.setAccepted(True)
self._on_tabSelect(0,True)
event.accept()
self.grab=False
if key=='Alt':
if self.closeKey:
self.closeKey=False
self.close()
if self.confKey:
self.confKey=False
#def keyReleaseEvent
def _set_focus(self,key):
cursor=QtGui.QCursor(Qt.PointingHandCursor)
self.setCursor(cursor)
self.grabMouse()
#cursor.setPos(50,50)
self.releaseMouse()
if key=="Space" or key=="NumLock+Enter" or key=="Return":
self.focusWidgets[self.currentBtn].clicked.emit()
elif key=="Tab":
tabCount=self.tabBar.count()
newTab=self.tabBar.currentIndex()+1
if newTab>tabCount:
newTab=0
self.tabBar.setCurrentIndex(newTab)
if key=="Alt":
return(True)
else:
self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE,BTN_SIZE))
self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE,BTN_SIZE))
if key=="Left":
self.currentBtn+=1
if self.currentBtn>=len(self.focusWidgets):
self.currentBtn=0
elif key=="Right":
self.currentBtn-=1
if self.currentBtn<0:
self.currentBtn=len(self.focusWidgets)-1
elif key=="Up":
currentBtn=self.currentBtn
currentBtn-=self.maxCol
if currentBtn<0:
currentBtn=self.currentBtn
self.currentBtn=currentBtn
elif key=="Down":
currentBtn=self.currentBtn
currentBtn+=self.maxCol
if currentBtn>=len(self.focusWidgets):
currentBtn=self.currentBtn
self.currentBtn=currentBtn
self.focusWidgets[self.currentBtn].setFocus()
self._debug("Focus to %s"%self.focusWidgets[self.currentBtn])
self.focusWidgets[self.currentBtn].resize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
self.focusWidgets[self.currentBtn].setIconSize(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
#def _set_focus(self,key):
def _get_focus(self,widget):
self.currentBtn=self.focusWidgets.index(widget)
#def _get_focus(self,widget)
def _add_widgets(self,vbox,apps):
row=int(len(self.appsWidgets)/self.maxCol)
col=(self.maxCol*(row+1))-len(self.appsWidgets)
sigmap_run=QSignalMapper(self)
sigmap_run.mapped[QString].connect(self._launch)
for appName,data in apps.items():
appIcon=data['Icon']
appDesc=data['Name']
if QtGui.QIcon.hasThemeIcon(appIcon):
icnApp=QtGui.QIcon.fromTheme(appIcon)
elif os.path.isfile(appIcon):
iconPixmap=QtGui.QPixmap(appIcon)
scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
icnApp=QtGui.QIcon(scaledIcon)
elif appIcon.startswith("http"):
if not os.path.isdir("%s/icons"%self.cache):
os.makedirs("%s/icons"%self.cache)
tmpfile=os.path.join("%s/icons"%self.cache,appIcon.split("/")[2].split(".")[0])
if not os.path.isfile(tmpfile):
try:
urlretrieve(appIcon,tmpfile)
except:
tmpfile=QtGui.QIcon.fromTheme("shell")
iconPixmap=QtGui.QPixmap(tmpfile)
scaledIcon=iconPixmap.scaled(QSize(BTN_SIZE*1.2,BTN_SIZE*1.2))
icnApp=QtGui.QIcon(scaledIcon)
appIcon=tmpfile
else:
continue
if not appName:
continue
self.app_icons[appName]=appIcon
self._debug("Adding %s"%appName)
btnApp=navButton(self)
btnApp.setIcon(icnApp)
btnApp.setIconSize(QSize(BTN_SIZE,BTN_SIZE))
btnApp.setToolTip(appDesc)
btnApp.setFocusPolicy(Qt.NoFocus)
btnApp.keypress.connect(self._set_focus)
btnApp.focusIn.connect(self._get_focus)
self.focusWidgets.append(btnApp)
self.appsWidgets.append(appName)
sigmap_run.setMapping(btnApp,appName)
btnApp.clicked.connect(sigmap_run.map)
vbox.addWidget(btnApp,row,col,Qt.Alignment(-1))
col+=1
if col==self.maxCol:
col=0
row+=1
#def _add_widgets
def _tabBar(self):
tabBar=QTabWidget()
tabScroll=QWidget()
tabScroll.setObjectName("scroll")
tabScroll.setStyleSheet("#scroll{background-image:url('%s');}"%self.bg)
tabScroll.setFocusPolicy(Qt.NoFocus)
scrollArea=QScrollArea(tabScroll)
scrollArea.setFocusPolicy(Qt.NoFocus)
tabContent=QWidget()
vbox=QGridLayout()
#scr=app.primaryScreen()
screenGeometry=app.screens()[0]
screenSize=screenGeometry.geometry()
w=screenSize.width()#-BTN_SIZE
h=screenSize.height()-(2*BTN_SIZE)
self.maxCol=1
neededSpace=0
while (neededSpace)=1360
if ((self.maxCol*BTN_SIZE>=neededSpace) and (w>=1360)):
self.maxCol-=1
#self.maxCol=int(w/BTN_SIZE)-2
self._debug("PrimarySize: {}".format(screenSize))
self._debug("ComponentsSize: {}x{}".format(screenSize.width(),screenSize.height()))
self._debug("CalculatedSize: {}x{}".format(w,h))
self._debug("NeededSpace: {}".format(neededSpace))
self._debug("Size: {0}\nCols: {1}".format(self.width(),self.maxCol))
for desktop in self.desktops:
apps=self._get_desktop_apps(desktop)
self._add_widgets(vbox,apps)
tabContent.setLayout(vbox)
scrollArea.setWidget(tabContent)
scrollArea.setObjectName("launcher")
scrollArea.alignment()
tabBar.addTab(tabScroll,"")
tabBar.tabBar().setTabButton(0,QTabBar.LeftSide,self.tab_id[0]['show'])
tabBar.tabBar().tabButton(0,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
scrollArea.setGeometry(QRect(0,0,w,h))
tabBar.setObjectName("tabbar")
return (tabBar)
#def _tabBar
def _on_tabChanged(self,remove=True):
self._debug("From Tab: %s"%self.currentTab)
index=self._get_tabId_from_index(self.currentTab)
self._debug("Tab Index: %s"%index)
key='show'
if self.currentTab==0:
index=0
key='close'
self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key])
self._debug("Pressed: %s"%self.tab_id[index][key])
try:
self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
except:
pass
self.runner.send_signal_to_thread("stop",self.tab_id[index]['thread'])
index=self._get_tabId_from_index(self.tabBar.currentIndex())
self.currentTab=self.tabBar.currentIndex()
key='close'
if self.currentTab==0:
self.focusWidgets[self.currentBtn].setFocus()
index=0
key='show'
self._debug("New Tab Index: %s"%self.tab_id[index])
self._debug("New index: %s"%index)
self._debug("Btn: %s"%self.tab_id[index][key])
self.tabBar.tabBar().setTabButton(self.currentTab,QTabBar.LeftSide,self.tab_id[index][key])
self.tabBar.tabBar().tabButton(self.currentTab,QTabBar.LeftSide).setFocusPolicy(Qt.NoFocus)
self.runner.send_signal_to_thread("cont",self.tab_id[index]['thread'])
#os.environ['DISPLAY']=self.tab_id[index]['display']
self._debug("New Current Tab: %s Icon:%s"%(self.currentTab,key))
#def _on_tabChanged
def _on_tabSelect(self,index,btn=None):
self._debug("Select tab: %s"%index)
if btn:
index=self._get_tabId_from_index(index)
self.tabBar.setCurrentIndex(self.tab_id[index]['index'])
cursor=QtGui.QCursor()
self.setCursor(cursor)
cursor.setPos(300,300)
#def _on_tabSelect
def _on_tabRemove(self,index):
self._debug("Remove tab: %s"%index)
self.tabBar.blockSignals(True)
self.tabBar.removeTab(self.tab_id[index]['index'])
xlockFile=os.path.join("/tmp",".X%s-lock"%self.tab_id[index]['display'].replace(":",""))
if os.path.isfile(xlockFile):
os.remove(xlockFile)
if (self.tab_id[index].get('app',"") in self.launched):
self.launched.remove(self.tab_id[index]['app'])
for idx in range(index+1,len(self.tab_id)):
if idx in self.tab_id.keys():
self._debug("Tab Array: %s"%self.tab_id)
if 'index' in self.tab_id[idx].keys():
self._debug("Reasign %s"%(self.tab_id[idx]['index']))
self.tab_id[idx]['index']=self.tab_id[idx]['index']-1
self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].statusBar.setText(str(self.tab_id[idx]['index']))
self.focusWidgets[self.appsWidgets.index(self.tab_id[idx]['app'])].index=self.tab_id[idx]['index']
self._debug("Reasigned %s -> %s"%(idx,self.tab_id[idx]['index']))
th=self.tab_id[index].get('thread',None)
if th:
self.runner.send_signal_to_thread("term",th)
xe=self.tab_id[index].get('xephyr',None)
if xe:
self.runner.send_signal_to_thread("term",xe)
dsp=self.tab_id[index].get('display',None)
self.focusWidgets[self.appsWidgets.index(self.tab_id[index]['app'])].statusBar.hide()
if dsp:
wid=self.tab_id[index].get('wid','')
if isinstance(wid,appZone):
wid=wid.wid.strip()
self.runner.stop_display(wid,dsp)
xlockFile=os.path.join("/tmp",".X%s-lock"%dsp.replace(":",""))
if os.path.isfile(xlockFile):
os.remove(xlockFile)
self.tab_id[index]={}
if self.launchErr:
self.currentTab=self._get_tabId_from_index(0)
else:
self.currentTab=self._get_tabId_from_index(self.tabBar.currentIndex())
index=self.currentTab
self._debug("New tab: %s"%self.currentTab)
self._on_tabChanged()
self.tabBar.blockSignals(False)
self.tabBar.setFocus()
self.tabBar.setCurrentIndex(index)
self._debug("Removed tab: %s"%index)
#def _on_tabRemove
def _get_category_apps(self,category):
apps=self.runner.get_category_apps(category.lower())
return (apps)
#def _get_category_apps
def _get_desktop_apps(self,desktop):
#Check if desktop is from run-o-matic
if "run-o-matic" in self.categories:
if os.path.isdir(self.runoapps):
if desktop in os.listdir(self.runoapps):
desktop=os.path.join(self.runoapps,desktop)
if os.path.isdir(self.userRunoapps):
if desktop in os.listdir(self.userRunoapps):
desktop=os.path.join(self.userRunoapps,desktop)
apps=self.runner.get_desktop_app(desktop,True)
return (apps)
#def _get_category_apps
def _launchZone(self,app):
tabContent=QWidget()
box=QVBoxLayout()
self._debug("Xephyr on {}".format(self.display))
wid=self.runner.get_wid("Xephyr on",self.display)
self._debug("Window Wid: %s"%wid)
zone=None
if wid:
(zone,zone_window)=appZone(tabContent).createZone(wid)
zone.setObjectName("appzone")
if not zone or not wid:
self._debug("Xephyr failed to launch")
self._debug("Zone: {}".format(zone))
self._debug("Wid: {}".format(wid))
tabContent.destroy()
wid=None
else:
time.sleep(3)
box.addWidget(zone)
zone.setFocusPolicy(Qt.NoFocus)
tabContent.setLayout(box)
icn=QtGui.QIcon.fromTheme(self.app_icons[app])
btn=QPushButton()
btn.setObjectName("PushButton")
btn.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
btn.setIcon(icn)
self.id+=1
self._debug("New Tab id %s"%self.id)
self.sigmap_tabSelect.setMapping(btn,self.id)
btn.clicked.connect(self.sigmap_tabSelect.map)
btn_close=QPushButton()
btn_close.setObjectName("PushButton")
btn_close.setIcon(self.closeIcon)
btn_close.setIconSize(QSize(TAB_BTN_SIZE,TAB_BTN_SIZE))
self.sigmap_tabRemove.setMapping(btn_close,self.id)
btn_close.clicked.connect(self.sigmap_tabRemove.map)
self.tab_id[self.id]={'index':self.tabBar.count(),'thread':None,'wid':zone_window,'show':btn,'close':btn_close,'display':self.display}
self.tabBar.addTab(tabContent,"")
return(wid)
#def _launchZone
def _launch(self,app):
if app in self.launched:
self._on_tabSelect(self.focusWidgets[self.appsWidgets.index(app)].index,True)
return
if app in self.blocked:
return
cursor=QtGui.QCursor(Qt.WaitCursor)
self.setCursor(cursor)
self.grabMouse()
self.tabBar.setTabIcon(0,self.previousIcon)
self._debug("Tab count: %s"%self.tabBar.count())
#Tabs BEFORE new tab is added
tabCount=self.tabBar.count()
btn=None
try:
btn=self.appsWidgets.index(app)
except:
btn=None
if btn:
self.currentBtn=btn
os.environ["HOME"]="/home/%s"%self.username
#os.environ["XAUTHORITY"]="/home/%s/.Xauthority"%self.username
self.display,self.pid,x_pid=self.runner.new_Xephyr(self.tabBar)
if self._launchZone(app):
self.tab_id[self.id]['thread']=self.runner.launch(app,self.display)
self.tab_id[self.id]['xephyr']=x_pid
self.tab_id[self.id]['app']=app
self.launched.append(app)
else:
if self.pid:
self.runner.send_signal_to_thread("kill",self.pid)
cursor=QtGui.QCursor(Qt.PointingHandCursor)
self.setCursor(cursor)
self.releaseMouse()
return
self.tabBar.setCurrentIndex(tabCount)
self.focusWidgets[self.appsWidgets.index(app)].index=tabCount
self.focusWidgets[self.appsWidgets.index(app)].statusBar.setText(str(tabCount))
self.focusWidgets[self.appsWidgets.index(app)].statusBar.label.setStyleSheet(self.focusWidgets[self.currentBtn].cssRun)
self.focusWidgets[self.appsWidgets.index(app)].statusBar.show(state='error')
cursor=QtGui.QCursor(Qt.PointingHandCursor)
self.releaseMouse()
self.setEnabled(True)
self.setCursor(cursor)
self.focusWidgets[self.appsWidgets.index(app)].setEnabled(True)
#def _launch
def _get_tabId_from_index(self,index):
idx=index
self._debug("Search id for display: %s"%(index))
self._debug("Current id: %s"%(self.tab_id))
for key,data in self.tab_id.items():
if 'index' in data.keys():
if index==data['index']:
idx=key
break
self._debug("Found tab idx: %s For selected index: %s"%(idx,index))
return idx
#def _get_tabId_from_index
def _get_tabId_from_thread(self,thread):
idx=-1
self._debug("Search id for thread: %s"%(thread))
self._debug("Current id: %s"%(self.tab_id))
for key,data in self.tab_id.items():
if 'thread' in data.keys():
if thread==data['thread']:
idx=key
break
self._debug("Found idx: %s For thread: %s"%(idx,thread))
return idx
#def _get_tabId_from_thread
def showMessage(self,msg,status="error",height=252):
return()
self.statusBar.height_=height
self.statusBar.setText(msg)
if status:
self.statusBar.show(state=status)
else:
self.statusBar.show(state=None)
#def _show_message
def _define_css(self):
css="""
#PushButton{
padding:10px;
margin:0px;
border:0px;
background-color:transparent;
}
#PushButton:active{
font: 14px Roboto;
color:black;
background:none;
background-color:transparent;
}
#PushButton:focus{
background:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 silver,stop:1 white);
border-radius:25px;
}
#launcher{
background-repeat:no-repeat;
background-position:center;
border:0px solid red;
}
#appzone{
background-color:transparent;
border:10px solid red;
}
"""
self._debug("Setting background: %s"%self.bg)
return(css)
#def _define_css
#class runomatic
app=QApplication(["Run-O-Matic"])
runomaticLauncher=runomatic()
app.exec_()