diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/INSTALL b/INSTALL old mode 100644 new mode 100755 index f7d460e..bdc6ed2 --- a/INSTALL +++ b/INSTALL @@ -1,66 +1,36 @@ -#! /bin/bash - ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- -# - -# This file is part of bumblebee-ui. +# This file is part of bumblebee-ui. # -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE -######################################## -#BE CAREFULL THIS IS A RELEASE CANDIDATE -######################################## -#NEED TO BE TESTED - -#Copy ecoptirun : a modified script to take up to date manually -#cp ecoptirun /usr/local/bin --> Moved to the bumblebee project.. - -#Copy software files /app, /icons, bumblebee-indicator in /usr/local/bin -cp -R app/ /usr/local/bin/app -cp -R icons/ /usr/local/bin/icons -cp bumblebee-indicator /usr/local/bin - -#Create bumblebee-app-settings a link to AppSettings.py -ln -s /usr/local/bin/app/AppSettings.py /usr/local/bin/bumblebee-app-settings - -#Copy desktop files in /usr/share/applications +cp icons/*.* /usr/share/icons/ +mkdir /usr/share/bumblebee-ui/ +cp app/*.* /usr/share/bumblebee-ui/ cp bumblebee-app-settings.desktop /usr/share/applications/ cp bumblebee-indicator.desktop /usr/share/applications/ +ln -s /usr/share/bumblebee-ui/AppSettings.py /usr/local/bin/bumblebee-app-settings +ln -s /usr/share/bumblebee-ui/Bumblebee-Indicator.py /usr/local/bin/bumblebee-indicator -#Copy or link icons in /usr/share/icons or /usr/share/pixmaps -cp icons/bumblebee.svg /usr/share/icons/ - -#Set the correct right for some files -chmod +x /usr/local/bin/bumblebee-indicator -chmod +x /usr/local/bin/app/* -chmod +x /usr/local/bin/ecoptirun chmod +x /usr/share/applications/bumblebee-app-settings.desktop chmod +x /usr/share/applications/bumblebee-indicator.desktop +chmod +x -R /usr/share/bumblebee-ui/ #TO DO MANUALLY #Add bumblebee-indicator at startup # # # -# - diff --git a/README b/README index ac4437f..ec894ae 100644 --- a/README +++ b/README @@ -1,39 +1,28 @@ -#! /bin/bash - ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- -# - -# This file is part of bumblebee-ui. +# This file is part of bumblebee-ui. # -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE -######################################## -#BE CAREFULL THIS IS A RELEASE CANDIDATE -######################################## -#NEED TO BE TESTED - -This user interface is a release candidate and one functionnality is still laking : be able to configure bumblebee. -This app allow you to configure applications desktop file to use ecoptirun script with different options. -This app contain also an experimental indicator that is far from optimal but works for the moment. +This is a user interface for bumblebee. +A complete explanation of the feature of this user interface are explained here : + https://github.com/Bumblebee-Project/bumblebee-ui/wiki +Please give it a try and report bugs to : + https://github.com/Bumblebee-Project/bumblebee-ui/issues +Thanks for your help. -Enjoy and please improve the code if you have time. +Instalation : just run INSTALL +Uninstalation : just run UNINSTALL in /usr/share/bumblee-iu/ diff --git a/app/AppSettings.py b/app/AppSettings.py index 4f47ec9..9f4db85 100755 --- a/app/AppSettings.py +++ b/app/AppSettings.py @@ -1,29 +1,21 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- +# This file is part of bumblebee-ui. # - -# This file is part of bumblebee-ui. -# -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE @@ -37,361 +29,332 @@ import Config from DesktopFile import DesktopFile, DesktopFileSet -#CREATE FILE IF NOT EXIST -if os.path.exists(Config.ui_config_file_path): - print "Using configuration file: " + Config.ui_config_file_path -else: - print "Configuration File does not exist, creating.." - configdir = os.path.dirname(Config.ui_config_file_directory) - try: - os.stat(configdir) - except: - os.mkdir(configdir) - - file = open(Config.ui_config_file_path, 'w') - file.write("[Bumblebee UI]\n") - file.write("preferred_app = []\n") - file.write("\n") - file.close() - - #ICON CONFIGURATION class IconSet: - """This small class contain the settings computed for categories""" - def __init__(self): - self.icon_theme= gtk.icon_theme_get_default() - self.icon_theme.append_search_path(Config.icon_file_directory) + """This small class contain the settings computed for categories""" + def __init__(self): + self.icon_theme= gtk.icon_theme_get_default() + self.icon_theme.append_search_path(Config.icon_file_directory) - def get_uri (self, icon_name, icon_size): - return "file://" + self.get_path(icon_name, icon_size) + def get_uri (self, icon_name, icon_size): + return "file://" + self.get_path(icon_name, icon_size) - def get_pixbuf (self, icon_name, icon_size=Config.icon_size): - try : - return self.icon_theme.load_icon(icon_name, icon_size, 0) - except : - try : return gtk.gdk.pixbuf_new_from_file_at_size(icon_name, icon_size, icon_size) - except : return self.icon_theme.load_icon(Config.default_icon_name, icon_size, 0) + def get_pixbuf (self, icon_name, icon_size=Config.icon_size): + try : + return self.icon_theme.load_icon(icon_name, icon_size, 0) + except : + try : return gtk.gdk.pixbuf_new_from_file_at_size(icon_name, icon_size, icon_size) + except : return self.icon_theme.load_icon(Config.default_icon_name, icon_size, 0) - def get_path (self, icon_name, icon_size=Config.icon_size): - try : return self.icon_theme.lookup_icon(icon_name, icon_size, 0).get_filename() - except : self.get_path(Config.default_icon_name) + def get_path (self, icon_name, icon_size=Config.icon_size): + try : return self.icon_theme.lookup_icon(icon_name, icon_size, 0).get_filename() + except : self.get_path(Config.default_icon_name) - -###TODO Factorize the init which is too long +#TODO Factorize the init which is too long class Applications_settings(): - #TODO This initialization should be avoid or rethinked - to_modify_file=[] - to_configure_file={} - to_unconfigure_file={} - categories_iter_with_child={} - configured_file_exist=False +#TODO This initialization should be avoid or rethinked + to_modify_file=[] + to_configure_file={} + to_unconfigure_file={} + categories_iter_with_child={} + configured_file_exist=False - def delete_event(self,widget,event,data=None): - return False - - ##FIXME There is a problem with subprocess: there is maybe a cleaner way to get it + def delete_event(self,widget,event,data=None): + return False - def destroy(self,widget): - if __name__=="__main__": gtk.main_quit() - else : self.window.destroy() - - def __init__(self, file_set=DesktopFileSet()): - #FIXME Seems not to be clean or shorter enough - self.file_set=file_set - - self.icon_set=IconSet() - ### MAIN WINDOW - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) - self.window.connect("delete_event", self.delete_event) - self.window.connect("destroy", self.destroy) - self.window.set_title("Bumblebee - Applications Settings") - self.window.set_border_width(0) - self.window.set_size_request(600,600) - ### MAIN WINDOW ICON : monitor and launcher - self.window.set_icon(self.icon_set.get_pixbuf('bumblebee', 48)) - - ### NOTEBOOK - self.notebook= gtk.Notebook() - self.notebook.set_tab_pos(gtk.POS_TOP) - self.notebook.set_border_width(10) - self.window.add(self.notebook) - - ### SELECT APPLICATION - ### LIST - self.build_app_list() - - ### VIEW - self.select_app_view = gtk.TreeView(self.app_list) - self.treeselection = self.select_app_view.get_selection() - self.treeselection.set_mode(gtk.SELECTION_NONE) - self.select_app_view.set_rules_hint(True) - self.select_app_view.show() - self.build_select_view() - - ### EXPAND CATEGORIES WITH CHILD - try : - for categorie in self.categories_iter_with_child: - categorie_iter,count= self.categories_iter_with_child[categorie] - self.select_app_view.expand_to_path(self.app_list.get_path(categorie_iter)) - except : self.select_app_view.expand_all() - - ### PAGE - self.select_page = self.build_notebook_page( - tab_title = "Select applications", - instruction_text = "Choose the application you want to configure to use with the discrete graphic card.", - view = self.select_app_view , - button_list = [self.action_button(stock=gtk.STOCK_APPLY, action=self.apply_app_set),self.action_button(label="Apply Now",action=self.apply_app_set)] ) - #TODO Create an apply now button which relaunch unity with unity --replace or use dynamic desktop configuration (See Ubuntu desktop specification) - - #CONFIGURE APPLICATION - ### LIST - self.configured_apps = self.app_list.filter_new(root=None) - self.configured_apps.set_visible_column(5) - - ### VIEW - self.config_app_view = gtk.TreeView(self.configured_apps) - self.config_app_view.set_rules_hint(True) - self.build_config_view() - self.config_app_view.show() - - ### PAGE - self.configure_page = self.build_notebook_page( - tab_title="Configure applications", - instruction_text="Choose the configuration for each application.\n Depending on mode the discrete card will be launched: always (Performance),\n only when plugged (Power Save) or with launcher shortcuts (Option)" , - view=self.config_app_view ) - - #FIXME : Set the focus on configure page doesn't work - if self.configured_file_exist==True: self.notebook.set_current_page(self.configure_page) - else: self.select_app_view.expand_all() + def destroy(self,widget): + if __name__=="__main__": gtk.main_quit() + else : self.window.destroy() - #SHOW ALL - self.window.show_all() + def __init__(self): + #FIXME Seems not to be clean or shorter enough + self.file_set=DesktopFileSet() + self.icon_set=IconSet() + # MAIN WINDOW + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window.connect("delete_event", self.delete_event) + self.window.connect("destroy", self.destroy) + self.window.set_title("Bumblebee - Applications Settings") + self.window.set_border_width(0) + self.window.set_size_request(600,600) + # MAIN WINDOW ICON : monitor and launcher + self.window.set_icon(self.icon_set.get_pixbuf('bumblebee', 48)) + + # NOTEBOOK + self.notebook= gtk.Notebook() + self.notebook.set_tab_pos(gtk.POS_TOP) + self.notebook.set_border_width(10) + self.window.add(self.notebook) + + # SELECT APPLICATION + # LIST + self.build_app_list() + + # VIEW + self.select_app_view = gtk.TreeView(self.app_list) + self.treeselection = self.select_app_view.get_selection() + self.treeselection.set_mode(gtk.SELECTION_NONE) + self.select_app_view.set_rules_hint(True) + self.select_app_view.show() + self.build_select_view() + + # EXPAND CATEGORIES WITH CHILD + try : + for categorie in self.categories_iter_with_child: + categorie_iter,count= self.categories_iter_with_child[categorie] + self.select_app_view.expand_to_path(self.app_list.get_path(categorie_iter)) + except : self.select_app_view.expand_all() + + # PAGE + self.select_page = self.build_notebook_page( + tab_title = "Select applications", + instruction_text = "Choose the application you want to configure to use with the discrete graphic card.", + view = self.select_app_view , + button_list = [self.action_button(stock=gtk.STOCK_APPLY, action=self.apply_app_set),self.action_button(label="Apply Now",action=self.apply_app_set)] ) +#TODO Create an apply now button which relaunch unity with unity --replace or use dynamic desktop configuration (See Ubuntu desktop specification) + # CONFIGURE APPLICATION + # LIST + self.configured_apps = self.app_list.filter_new(root=None) + self.configured_apps.set_visible_column(5) + + # VIEW + self.config_app_view = gtk.TreeView(self.configured_apps) + self.config_app_view.set_rules_hint(True) + self.build_config_view() + self.config_app_view.show() + + # PAGE + self.configure_page = self.build_notebook_page( + tab_title="Configure applications", + instruction_text="Choose the configuration for each application.\n Depending on mode the discrete card will be launched: always (Performance),\n only when plugged (Power Save) or with launcher shortcuts (Option)" , + view=self.config_app_view ) + +#FIXME : Set the focus on configure page doesn't work + if self.configured_file_exist==True: self.notebook.set_current_page(self.configure_page) + else: self.select_app_view.expand_all() + + #SHOW ALL + self.window.show_all() + + def build_notebook_page(self , tab_title, instruction_text , view , button_list=[]): + # INSTRUCTION FRAME + instruction_frame = self.return_instruction_frame(instruction_text) + # SCROLL FRAME + scrolled_window=gtk.ScrolledWindow() + scrolled_window.add(view) + # ACTION AREA + hbox=gtk.HBox(homogeneous=False, spacing=10) + # SPECIFIC BUTTON + for button in button_list : hbox.pack_start(button, False, False, 10) + # CLOSE BUTTON + hbox.pack_end(self.action_button(stock=gtk.STOCK_CLOSE, action=self.destroy), False, False, 10) + # CONTAINER + vbox= gtk.VBox(homogeneous=False) + vbox.pack_start(instruction_frame, False, False, 0) + vbox.pack_start(scrolled_window, True, True, 10) + vbox.pack_end(hbox, False, False, 10) + # NOTEBOOK PAGE + notebook_label= gtk.Label(tab_title) + return self.notebook.append_page(vbox, notebook_label) + + def return_instruction_frame(self,text): + """Return an instruction frame with the text given""" + instruction_frame = gtk.Frame() + instruction = gtk.Label(text) + #instruction.set_justify(gtk.JUSTIFY_LEFT) + instruction_frame.set_border_width(10) +#FIXME : There is a problem with the instruction frame text rendering : use size_allocate to dynamicaly set the size of label + #instruction.set_line_wrap(True) + instruction_frame.add(instruction) + return instruction_frame + + def action_button(self, action, label=None, stock=None): + button= gtk.Button(label,stock) + button.set_size_request(width=100,height=-1) + button.connect("clicked",action) + return button - def build_notebook_page(self , tab_title, instruction_text , view , button_list=[]): - ### INSTRUCTION FRAME - instruction_frame = self.return_instruction_frame(instruction_text) - ### SCROLL FRAME - scrolled_window=gtk.ScrolledWindow() - scrolled_window.add(view) - ### ACTION AREA - hbox=gtk.HBox(homogeneous=False, spacing=10) - ### SPECIFIC BUTTON - for button in button_list : hbox.pack_start(button, False, False, 10) - ### CLOSE BUTTON - hbox.pack_end(self.action_button(stock=gtk.STOCK_CLOSE, action=self.destroy), False, False, 10) - ### CONTAINER - vbox= gtk.VBox(homogeneous=False) - vbox.pack_start(instruction_frame, False, False, 0) - vbox.pack_start(scrolled_window, True, True, 10) - vbox.pack_end(hbox, False, False, 10) - ### NOTEBOOK PAGE - notebook_label= gtk.Label(tab_title) - return self.notebook.append_page(vbox, notebook_label) - - def return_instruction_frame(self,text): - """Return an instruction frame with the text given""" - instruction_frame = gtk.Frame() - instruction = gtk.Label(text) - #instruction.set_justify(gtk.JUSTIFY_LEFT) - instruction_frame.set_border_width(10) - #FIXME : There is a problem with the instruction frame text rendering : use size_allocate to dynamicaly set the size of label - #instruction.set_line_wrap(True) - instruction_frame.add(instruction) - return instruction_frame - - def action_button(self, action, label=None, stock=None): - button= gtk.Button(label,stock) - button.set_size_request(width=100,height=-1) - button.connect("clicked",action) - return button - - def build_app_list(self): - """Function to build the store with this columns : - *Application Name or Categorie Name, *File Name, *Application Categorie, *Application Icon Path, Is Not Category, Configured, (Selected by default), Mode, 32bits, Compression, Background display, Background color - for categories : Categorie Name, None, None, Icon Path, False , Has child configured, False, None, - """ - self.app_list = gtk.TreeStore(str,str,str,gtk.gdk.Pixbuf,bool,bool,bool,str,bool,str,bool,str) - self.categorie_iter={} - #CATEFORIE ROWS - for [categorie, icon_name] in Config.categorie_list + [Config.unmatch_categorie, Config.uncategorized_categorie]: - iter=self.app_list.append(None, [categorie,None,None] + [self.icon_set.get_pixbuf(icon_name)] + 3*[False] + 3*[None] + [False, Config.to_configure_color]) - self.categorie_iter.update({categorie:iter}) - #APPLICATIONS ROWS - for app_info in self.file_set.get_apps_info(): - parent_iter=self.categorie_iter.get(app_info[2]) - app_info[3]=self.icon_set.get_pixbuf(app_info[3]) - if app_info[5] == True: - self.configured_file_exist=True - self.app_list.append(parent_iter, app_info + [True, Config.configured_color]) - self.app_list[parent_iter][5]=True #Used to only show the categories with configured child - self.add_child_for_categorie(app_info[2]) - else : self.app_list.append(parent_iter, app_info + [False, Config.to_configure_color]) + def build_app_list(self): + """Function to build the store with this columns : + *Application Name or Categorie Name, *File Name, *Application Categorie, *Application Icon Path, Is Not Category, Configured, (Selected by default), Mode, 32bits, Compression, Background display, Background color + for categories : Categorie Name, None, None, Icon Path, False , Has child configured, False, None, + """ + self.app_list = gtk.TreeStore(str,str,str,gtk.gdk.Pixbuf,bool,bool,bool,str,bool,str,bool,str) + self.categorie_iter={} + # CATEFORIE ROWS + for [categorie, icon_name] in Config.categorie_list + [Config.unmatch_categorie, Config.uncategorized_categorie]: + iter=self.app_list.append(None, [categorie,None,None] + [self.icon_set.get_pixbuf(icon_name)] + 3*[False] + 3*[None] + [False, Config.to_configure_color]) + self.categorie_iter.update({categorie:iter}) + # APPLICATIONS ROWS + for app_info in self.file_set.get_apps_info(): + parent_iter=self.categorie_iter.get(app_info[2]) + app_info[3]=self.icon_set.get_pixbuf(app_info[3]) + if app_info[5] == True: + self.configured_file_exist=True + self.app_list.append(parent_iter, app_info + [True, Config.configured_color]) + self.app_list[parent_iter][5]=True #Used to only show the categories with configured child + self.add_child_for_categorie(app_info[2]) + else : self.app_list.append(parent_iter, app_info + [False, Config.to_configure_color]) - def build_select_view(self): - #APPLICATION OR CATEGORIE NAME COLUMN - self.column = gtk.TreeViewColumn('Applications') + def build_select_view(self): + # APPLICATION OR CATEGORIE NAME COLUMN + self.column = gtk.TreeViewColumn('Applications') - #SELECT CHECKBOX - rendererToggle = gtk.CellRendererToggle() - self.column.pack_start(rendererToggle, True) - self.column.set_attributes(rendererToggle, active=6, visible=4) - rendererToggle.set_property('activatable', True) - rendererToggle.connect('toggled', self.on_select_app) + # SELECT CHECKBOX + rendererToggle = gtk.CellRendererToggle() + self.column.pack_start(rendererToggle, True) + self.column.set_attributes(rendererToggle, active=6, visible=4) + rendererToggle.set_property('activatable', True) + rendererToggle.connect('toggled', self.on_select_app) - #APPLICATION ICON - rendererIcon=gtk.CellRendererPixbuf() - self.column.pack_start(rendererIcon, True) - self.column.set_attributes(rendererIcon, pixbuf=3) + # APPLICATION ICON + rendererIcon=gtk.CellRendererPixbuf() + self.column.pack_start(rendererIcon, True) + self.column.set_attributes(rendererIcon, pixbuf=3) - #APPLICATION NAME - rendererText=gtk.CellRendererText() - self.column.pack_start(rendererText, True) - self.column.set_attributes(rendererText, text=0) + # APPLICATION NAME + rendererText=gtk.CellRendererText() + self.column.pack_start(rendererText, True) + self.column.set_attributes(rendererText, text=0) - #BACKGROUND COLOR SET - for renderer in [rendererToggle, rendererIcon, rendererText]: - self.column.add_attribute(renderer, "cell-background-set", 10) - self.column.add_attribute(renderer, "cell-background", 11) - - #self.column.set_min_width(300) : cause problem with the pack start - ###FIXME : the style must be refined in order to have a better display for the user : size if the icon, name of the application and categorie display - ###FIXME : there must be a better way also to change the row color depending on state (this color can be changed by a color id that can be used to know the state of the application ??? in order to have a smoother treestore - self.column.set_max_width(350) + # BACKGROUND COLOR SET + for renderer in [rendererToggle, rendererIcon, rendererText]: + self.column.add_attribute(renderer, "cell-background-set", 10) + self.column.add_attribute(renderer, "cell-background", 11) + #self.column.set_min_width(300) : cause problem with the pack start +#FIXME : the style must be refined in order to have a better display for the user : size if the icon, name of the application and categorie display +#FIXME : there must be a better way also to change the row color depending on state (this color can be changed by a color id that can be used to know the state of the application ??? in order to have a smoother treestore + self.column.set_max_width(350) - #FILE NAME COLUMN - self.select_app_view.append_column(self.column) - rendererText2=gtk.CellRendererText() - column1 = gtk.TreeViewColumn('File Name',rendererText2, text=1) - column1.add_attribute(rendererText2, "cell-background-set",10) - column1.add_attribute(rendererText2, "cell-background",11) - self.select_app_view.append_column(column1) - self.select_app_view.set_level_indentation(10) - - def on_select_app(self, cell, row): - ###TODO : Find a better way to manage row color change - self.app_list[row][6] = not self.app_list[row][6] - Configured, Selected= self.app_list[row][5], self.app_list[row][6] - if Configured==True and Selected==False : #The app will be Unconfigured - self.app_list[row][10],self.app_list[row][11]=True,Config.to_unconfigure_color - self.to_unconfigure_file.update({self.app_list[row][1]:self.app_list.get_iter(row)}) - elif Configured==True and Selected==True : #The configured app is reselected: Nothing to apply - self.app_list[row][10],self.app_list[row][11]=True,Config.configured_color - del self.to_unconfigure_file[self.app_list[row][1]] - elif Configured==False and Selected==True : #The app will be Configured - self.app_list[row][10],self.app_list[row][11]=True,Config.to_configure_color - self.to_configure_file.update({self.app_list[row][1]:self.app_list.get_iter(row)}) - elif Configured==False and Selected==False : #The app is not configured and unselected: Nothing to apply - self.app_list[row][10]=False - del self.to_configure_file[self.app_list[row][1]] + #FILE NAME COLUMN + self.select_app_view.append_column(self.column) + rendererText2=gtk.CellRendererText() + column1 = gtk.TreeViewColumn('File Name',rendererText2, text=1) + column1.add_attribute(rendererText2, "cell-background-set",10) + column1.add_attribute(rendererText2, "cell-background",11) + self.select_app_view.append_column(column1) + self.select_app_view.set_level_indentation(10) + + def on_select_app(self, cell, row): +#TODO : Find a better way to manage row color change + self.app_list[row][6] = not self.app_list[row][6] + Configured, Selected= self.app_list[row][5], self.app_list[row][6] + if Configured==True and Selected==False : #The app will be Unconfigured + self.app_list[row][10],self.app_list[row][11]=True,Config.to_unconfigure_color + self.to_unconfigure_file.update({self.app_list[row][1]:self.app_list.get_iter(row)}) + elif Configured==True and Selected==True : #The configured app is reselected: Nothing to apply + self.app_list[row][10],self.app_list[row][11]=True,Config.configured_color + del self.to_unconfigure_file[self.app_list[row][1]] + elif Configured==False and Selected==True : #The app will be Configured + self.app_list[row][10],self.app_list[row][11]=True,Config.to_configure_color + self.to_configure_file.update({self.app_list[row][1]:self.app_list.get_iter(row)}) + elif Configured==False and Selected==False : #The app is not configured and unselected: Nothing to apply + self.app_list[row][10]=False + del self.to_configure_file[self.app_list[row][1]] - def build_config_view(self): - """Function to create a setting list for applications configured for Bumblebee""" - #APPLICATION NAME COLUMNS - self.column=gtk.TreeViewColumn('Applications') - - rendererIcon=gtk.CellRendererPixbuf() - self.column.pack_start(rendererIcon, False) - self.column.set_attributes(rendererIcon, pixbuf=3) - rendererText=gtk.CellRendererText() - self.column.pack_start(rendererText, False) - self.column.set_attributes(rendererText, text=0) + def build_config_view(self): + """Function to create a setting list for applications configured for Bumblebee""" + # APPLICATION NAME COLUMNS + self.column=gtk.TreeViewColumn('Applications') + + rendererIcon=gtk.CellRendererPixbuf() + self.column.pack_start(rendererIcon, False) + self.column.set_attributes(rendererIcon, pixbuf=3) + rendererText=gtk.CellRendererText() + self.column.pack_start(rendererText, False) + self.column.set_attributes(rendererText, text=0) + + self.config_app_view.append_column(self.column) + + # MODE, DRIVER AND COMPRESSION COLUMN + self.build_combo_column("Mode", 7, Config.mode_keys.values()) + + self.build_config_column ("32bits Driver", 8) + + self.build_combo_column("Compression", 9, ["default"] + Config.compression_list) + + self.config_app_view.set_level_indentation(20) + self.config_app_view.expand_all() + self.config_app_view.set_show_expanders(False) - self.config_app_view.append_column(self.column) - - # MODE, DRIVER AND COMPRESSION COLUMN - self.build_combo_column("Mode", 7, Config.mode_keys.values()) + def build_combo_column(self, column_name, column_id, value_list): + """Function to build the columns with selection from a list""" + # COMBOBOX CELL + combo_list = gtk.ListStore(str) + for value in value_list: combo_list.append([value]) + rendererCombo = gtk.CellRendererCombo() + rendererCombo.set_properties(model=combo_list, editable=True) + rendererCombo.set_property("has-entry", False) + rendererCombo.set_property("text-column", 0) + rendererCombo.connect("edited", self.on_combo_edit, column_id) + # COMBOBOX COLUMN + column = gtk.TreeViewColumn(column_name) + column.pack_start(rendererCombo, False) + column.add_attribute(rendererCombo, "text", column_id) + column.add_attribute(rendererCombo, "visible", 4) + self.config_app_view.append_column(column) - self.build_config_column ("32bits Driver", 8) + def on_combo_edit(self, cell , path , new_text, column_id): + filter_iter = self.configured_apps.get_iter(path) + iter = self.configured_apps.convert_iter_to_child_iter(filter_iter) + self.app_list[iter][column_id] = new_text + DesktopFile(self.app_list[iter][1]).set_exec_config(self.app_list[iter][7],self.app_list[iter][8],self.app_list[iter][9]) - self.build_combo_column("Compression", 9, ["default"] + Config.compression_list) - - self.config_app_view.set_level_indentation(20) - self.config_app_view.expand_all() - self.config_app_view.set_show_expanders(False) - - def build_combo_column(self, column_name, column_id, value_list): - """Function to build the columns with selection from a list""" - # COMBOBOX CELL - combo_list = gtk.ListStore(str) - for value in value_list: combo_list.append([value]) - rendererCombo = gtk.CellRendererCombo() - rendererCombo.set_properties(model=combo_list, editable=True) - rendererCombo.set_property("has-entry", False) - rendererCombo.set_property("text-column", 0) - rendererCombo.connect("edited", self.on_combo_edit, column_id) - # COMBOBOX COLUMN - column = gtk.TreeViewColumn(column_name) - column.pack_start(rendererCombo, False) - column.add_attribute(rendererCombo, "text", column_id) - column.add_attribute(rendererCombo, "visible", 4) - self.config_app_view.append_column(column) - - def on_combo_edit(self, cell , path , new_text, column_id): - filter_iter = self.configured_apps.get_iter(path) - iter = self.configured_apps.convert_iter_to_child_iter(filter_iter) - self.app_list[iter][column_id] = new_text - DesktopFile(self.app_list[iter][1]).set_exec_config(self.app_list[iter][7],self.app_list[iter][8],self.app_list[iter][9]) - - def build_config_column(self, column_name, column_id): - rendererToggle = gtk.CellRendererToggle() - rendererToggle.set_property('activatable', True) - rendererToggle.connect('toggled', self.on_config_check ,(self.configured_apps, column_id)) - - column = gtk.TreeViewColumn(column_name, rendererToggle, active=column_id, visible=4) - self.config_app_view.append_column(column) - - def on_config_check(self, cell, row, user_data): - model, column = user_data - iter = model.convert_iter_to_child_iter(model.get_iter(row)) - self.app_list[iter][column] = not self.app_list[iter][column] - DesktopFile(self.app_list[iter][1]).set_exec_config(self.app_list[iter][7],self.app_list[iter][8],self.app_list[iter][9]) + def build_config_column(self, column_name, column_id): + rendererToggle = gtk.CellRendererToggle() + rendererToggle.set_property('activatable', True) + rendererToggle.connect('toggled', self.on_config_check ,(self.configured_apps, column_id)) + + column = gtk.TreeViewColumn(column_name, rendererToggle, active=column_id, visible=4) + self.config_app_view.append_column(column) + def on_config_check(self, cell, row, user_data): + model, column = user_data + iter = model.convert_iter_to_child_iter(model.get_iter(row)) + self.app_list[iter][column] = not self.app_list[iter][column] + DesktopFile(self.app_list[iter][1]).set_exec_config(self.app_list[iter][7],self.app_list[iter][8],self.app_list[iter][9]) - def apply_app_set (self,widget,data=None): - for file_name, iter in self.to_configure_file.iteritems(): #The app is to configure - self.apply_app_change ( iter, file_name, [self.file_set.configure_file, self.add_child_for_categorie], - True, Config.mode_keys['option'], True, Config.configured_color) - self.to_configure_file.clear() - - for file_name, iter in self.to_unconfigure_file.iteritems(): #The app is to unconfigure - self.apply_app_change ( iter, file_name, [self.file_set.unconfigure_file, self.remove_child_for_categorie], - False, False, False, Config.to_configure_color) - self.to_unconfigure_file.clear() - self.config_app_view.expand_all() - self.file_set.store_configured() + def apply_app_set (self,widget,data=None): + for file_name, iter in self.to_configure_file.iteritems(): #The app is to configure + self.apply_app_change ( iter, file_name, [self.file_set.configure_file, self.add_child_for_categorie], + True, Config.mode_keys['option'], True, Config.configured_color) + self.to_configure_file.clear() + for file_name, iter in self.to_unconfigure_file.iteritems(): #The app is to unconfigure + self.apply_app_change ( iter, file_name, [self.file_set.unconfigure_file, self.remove_child_for_categorie], + False, False, False, Config.to_configure_color) + self.to_unconfigure_file.clear() + self.config_app_view.expand_all() - def apply_app_change (self, iter, file_name, actions, configured, mode, display_bg, bg_color): - actions[0](file_name) - self.app_list.set(iter, 5, configured, 7, mode, 10, display_bg, 11, bg_color) - actions[1](self.app_list.get_value(iter,2)) + def apply_app_change (self, iter, file_name, actions, configured, mode, display_bg, bg_color): + actions[0](file_name) + self.app_list.set(iter, 5, configured, 7, mode, 10, display_bg, 11, bg_color) + actions[1](self.app_list.get_value(iter,2)) - #TODO There must be thing done to deal with categories having configured child : simplify this function - def add_child_for_categorie(self,categorie_name): - if self.categories_iter_with_child.has_key(categorie_name): - parent_iter, count=self.categories_iter_with_child[categorie_name] - child_count= count+1 - else: - child_count=1 - self.categories_iter_with_child.update({categorie_name:[self.categorie_iter[categorie_name], child_count]}) - self.app_list.set(self.categorie_iter[categorie_name], 5, True) +#TODO There must be thing done to deal with categories having configured child : simplify this function + def add_child_for_categorie(self,categorie_name): + if self.categories_iter_with_child.has_key(categorie_name): + parent_iter, count=self.categories_iter_with_child[categorie_name] + child_count= count+1 + else: child_count=1 + self.categories_iter_with_child.update({categorie_name:[self.categorie_iter[categorie_name], child_count]}) + self.app_list.set(self.categorie_iter[categorie_name], 5, True) - def remove_child_for_categorie(self,categorie_name): - if self.categories_iter_with_child.has_key(categorie_name): - parent_iter, count=self.categories_iter_with_child[categorie_name] - child_count= count-1 - self.categories_iter_with_child.update({categorie_name:[parent_iter, child_count]}) - if child_count==0 : - del self.categories_iter_with_child[categorie_name] - self.app_list.set(self.categorie_iter[categorie_name], 5, False) + def remove_child_for_categorie(self,categorie_name): + if self.categories_iter_with_child.has_key(categorie_name): + parent_iter, count=self.categories_iter_with_child[categorie_name] + child_count= count-1 + self.categories_iter_with_child.update({categorie_name:[parent_iter, child_count]}) + if child_count==0 : + del self.categories_iter_with_child[categorie_name] + self.app_list.set(self.categorie_iter[categorie_name], 5, False) - def main(self): - gtk.main() - return 0 + def main(self): + gtk.main() + return 0 if __name__=="__main__": - Appset = Applications_settings() - Appset.main() - + Appset = Applications_settings() + Appset.main() diff --git a/app/Bumblebee-Indicator.py b/app/Bumblebee-Indicator.py new file mode 100755 index 0000000..bcdfd06 --- /dev/null +++ b/app/Bumblebee-Indicator.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# +# This file is part of bumblebee-ui. +# +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . +# +### END LICENSE + +# UI MODULE +import pygtk +pygtk.require('2.0') +import gtk +import gobject +import appindicator +import pynotify + +# SYSTEM MODULE +import os +import subprocess + +# ORIGINAL CLASS +import Config +from AppSettings import Applications_settings, IconSet +from DesktopFile import DesktopFile, DesktopFileSet + +class BumblebeeIndicator(): +#TODO There must be a better way to get the icon than the URI +#FIXME The notification must be replaced when still visible + def notify_state(self, title, msg, icon_name): + pynotify.init("Bumblebee notification") + self.notification= pynotify.Notification(title, msg, IconSet().get_uri(icon_name,48)) + self.notification.set_urgency(pynotify.URGENCY_CRITICAL) + #FIXME The notification is to slow and this doesn't work + #self.notification.set_timeout(1000) + self.notification.show() + +# INITIALIZATION OF INDICATOR AND MENU + def __init__(self): + self.ind = appindicator.Indicator ("bumblebee-indicator", "bumblebee-indicator", appindicator.CATEGORY_HARDWARE) + self.ind.set_status (appindicator.STATUS_ACTIVE) +#TODO The icons style and accesibility must be enhanced : see icons/test directory + self.ind.set_icon("bumblebee-indicator.svg",'Bumblebee is unactive') + self.ind.set_attention_icon ("bumblebee-indicator-active.svg",'Bumblebee is active') + + self.card_state=False + self.lock_file = "/tmp/.X%s-lock" % Config.vgl_display + + self.build_menu() + + self.menu.show() + self.ind.set_menu(self.menu) + + def quit(self, widget, data=None): + gtk.main_quit() + + def build_menu(self): + self.menu = gtk.Menu() + self.switch = gtk.CheckMenuItem() + self.initial_state_checker() + self.switch.set_sensitive(False) + self.menu.append(self.switch) + + self.build_menu_separator(self.menu) + + self.prefered_app_submenu = gtk.MenuItem("Preferred Apps") + self.update_menu() + self.prefered_app_submenu.connect('activate', self.update_menu) + self.menu.append(self.prefered_app_submenu) + + item2 = gtk.MenuItem("Configure Apps") + item2.connect("activate", self.app_configure) + self.menu.append(item2) + +#TODO An UI to configure Bumblebee would be nice + + self.build_menu_separator(self.menu) + + quit = gtk.ImageMenuItem(gtk.STOCK_QUIT) + quit.connect("activate", self.quit) + self.menu.append(quit) + + self.menu.show_all() + + def build_menu_separator(self, menu): + separator = gtk.SeparatorMenuItem() + separator.show() + menu.append(separator) + +# FUNCTIONS TO BUILD THE "PREFERRED APP" MENU FROM THE LOCAL DESKTOP FILES + def update_menu(self, widget=None): + pref_menu=gtk.Menu() + self.add_submenu_items( pref_menu, Config.default_preferred_apps ) + self.build_menu_separator( pref_menu ) + self.add_submenu_items( pref_menu, DesktopFileSet().get_configured_from_check() ) + pref_menu.show() + self.prefered_app_submenu.set_submenu(pref_menu) + + def add_submenu_items(self, submenu, items_list): + for Name, Exec_list in items_list : + subitem = gtk.MenuItem(label=Name) + subitem.connect("activate", self.call_app, Exec_list) + subitem.show() + submenu.append( subitem ) + +# FUNCTIONS TO CHECK FOR THE STATE OF THE INDICATOR + def initial_state_checker(self): + if self.attention_state_condition(): self.set_attention_state(notify=False) + else : self.set_active_state(notify=False) + + def state_checker(self): + if self.attention_state_condition(): + if self.card_state == False : self.set_attention_state() + elif self.card_state == True: self.set_active_state() + return True + + def attention_state_condition(self): + if os.path.exists(self.lock_file): return True + else: return False + +# FUNCTIONS TO SET THE STATE OF THE INDICATOR AND LAUNCH NOTIFICATION + def set_attention_state(self, notify=True): + self.set_status(True, appindicator.STATUS_ATTENTION, + Config.attention_label, + Config.attention_comment, + "bumblebee-indicator-active", notify) + + def set_active_state(self, notify=True): + self.set_status(False, appindicator.STATUS_ACTIVE, + Config.active_label, + Config.active_comment, + "bumblebee-indicator", notify) + + def set_status(self, status, indicator, label, comment, icon, notify): + self.ind.set_status(indicator) + self.card_state = status + if notify == True: self.notify_state(label, comment, icon) + self.switch.set_label(label) + self.switch.set_active(status) + +# FUNCTION TO DEFINE THE APPLICATIONS SETTING LINK IN THE INDICATOR + + def app_configure(self,widget): + Applications_settings() + +# FUNCTION TO LAUNCH THE APPS WITHIN THE INDICATOR + def call_app(self, widget, app_exec): +#FIXME There is a problem when closing the launched app and when the indicator has been closed: the indicator is still running : What a daemon!! + subprocess.Popen(app_exec,shell=False) + +# MAIN LOOP LAUNCHING A STATE CHECK EVERY TWO SECONDS + def main(self): + self.state_checker() +#FIXME It would be nice to avoid this loop : Maybe by using a signal emitted by the system + #gtk.timeout_add(2000,self.state_checker) + gobject.timeout_add_seconds(2, self.state_checker) + gtk.main() + +if __name__ == "__main__": + indicator = BumblebeeIndicator() + indicator.main() + diff --git a/app/Config.py b/app/Config.py old mode 100755 new mode 100644 index fe82ed9..921f618 --- a/app/Config.py +++ b/app/Config.py @@ -1,37 +1,26 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- +# This file is part of bumblebee-ui. # - -# This file is part of bumblebee-ui. -# -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE import os -import glob -import re import gtk -import ConfigParser #DESKTOP FILES PATH user_home_directory = os.path.expanduser('~') @@ -40,11 +29,7 @@ global_desktop_file_directory = '/usr/share/applications/' #ICONS FILE PATH -icon_file_directory = '/usr/share/bumblebee-ui/icons/' - -#CONFIG FILES PATH -ui_config_file_directory = user_desktop_file_directory -ui_config_file_path = ui_config_file_directory + 'bumblebee-ui.cfg' +icon_file_directory = '/usr/share/icons/hicolor/48x48/apps/' #ACCEPTED COMPRESSION compression_list=['jpeg','proxy','rgb','yuv','xv'] @@ -52,8 +37,8 @@ #MODE LIST #TODO Need to be set to a list maybe mode_keys={'perf':"Performance", - 'eco':"Power Save", - 'option':"Optional"} + 'eco':"Power Save", + 'option':"Optional"} #ICON FILES THEME icon_size=24 @@ -65,27 +50,46 @@ to_unconfigure_color='#FF0033' #BUMBLEBEE DEFAULT CONFIGURATION -config_file_path='/etc/default/bumblebee' -[default_compression]=[re.sub(r'VGL_COMPRESS=(.*)\n', r'\1', line) for line in open(config_file_path) if 'VGL_COMPRESS' in line] +config_file_path='/etc/bumblebee/bumblebee.conf' +#GET BUMBLEBEE CONFIGURATION VALUE +def get_config_value(variable_name): + """Function to get configuration value inside a shell script""" + for line in open(config_file_path): + if variable_name in line: + return line.split('=',1)[1].replace("\n","") + +default_compression= get_config_value('VGL_COMPRESS') +vgl_display= get_config_value('VirtualDisplay').replace(":","") #CATEGORIES CONFIGURATION ####TODO There might be a way to get those key from a menu configuration file categorie_list=[['Game', 'applications-games'], - ['AudioVideo', 'applications-multimedia'], - ['Graphics', 'applications-graphics'], - ['Network', 'applications-internet'], - ['Office', 'applications-office'], - ['Settings', 'applications-system'], - ['System', 'applications-electronics'], - ['Utility', 'applications-utilities']] + ['AudioVideo', 'applications-multimedia'], + ['Graphics', 'applications-graphics'], + ['Network', 'applications-internet'], + ['Office', 'applications-office'], + ['Settings', 'applications-system'], + ['System', 'applications-electronics'], + ['Utility', 'applications-utilities']] unmatch_categorie=['Miscellaneous','applications-other'] uncategorized_categorie=['Uncategorized', 'application-x-executable'] +#DEFAULT APPLICATIONS IN THE PREFERRED APP MENU : +default_preferred_apps =[ ['Glxgears', ['optirun', 'glxgears']] , + ['Glxspheres', ['optirun', 'glxspheres']] ] + +#NOTIFICATION MESSAGES : +#TODO Revert when the possibility to turn off the card is back +attention_label="Bumblebee : ON" +attention_comment="Bumblebee is in use" +active_label="Bumblebee : OFF" +active_comment="Bumblebee is not used anymore" + #TODO : There might be a way to use string formatting to simplify the config definition #FIXME There must be a better way to store config if __name__=="__main__" : - print "Config.py can't run as a standalone application" - quit() + print "Config.py can't run as a standalone application" + quit() diff --git a/app/DesktopFile.py b/app/DesktopFile.py index 8bce3c9..daa20ee 100755 --- a/app/DesktopFile.py +++ b/app/DesktopFile.py @@ -1,29 +1,21 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- +# This file is part of bumblebee-ui. # - -# This file is part of bumblebee-ui. -# -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE @@ -44,266 +36,244 @@ #FIXME Clean this class and find shorter and cleaner way class DesktopFileSet: - """This class contain list of files in local directory or in global directory and a list of configured files""" - def __init__(self): - self.get_local() - self.get_global() - self.get_configured_from_cfg() - - def get_desktop_file_set(self, directory): - return set([re.sub(r'(.*)\.desktop',r'\1',x) for x in glob.glob1( directory, '*.desktop' )]) - - def get_local (self): - self.local_set = self.get_desktop_file_set(Config.user_desktop_file_directory) + """This class contain list of files in local directory or in global directory and a list of configured files""" + def __init__(self): + self.get_local() + self.get_global() - def get_global (self): - self.global_set = self.get_desktop_file_set(Config.global_desktop_file_directory) - self.local_set + def get_desktop_file_set(self, directory): + return set([re.sub(r'(.*)\.desktop',r'\1',x) for x in glob.glob1( directory, '*.desktop' )]) + + def get_local (self): + self.local_set = self.get_desktop_file_set(Config.user_desktop_file_directory) - ##CAN BE TEST SO THERE IS NO CONFIGURATION FILE BUT THE UPLOAD IS LONGER - #FIXME The preferred app list should be check dynamically with a special function to parse user file( conf is needed when bumblebee indicator change -# def get_configured_from_check (self): -# self.configured_set = set([ app for app in list(self.local_set) if DesktopFile(app).is_configured() ]) + def get_global (self): + self.global_set = self.get_desktop_file_set(Config.global_desktop_file_directory) - self.local_set - def get_configured_from_cfg (self): - self.open_cfg() - self.configured_set = set(eval(self.ui_config.get('Bumblebee UI','preferred_app'))) - - def open_cfg (self): - self.ui_config = ConfigParser.RawConfigParser() - self.ui_config.read(Config.ui_config_file_path) - - def store_configured (self): - self.open_cfg() - self.ui_config.set('Bumblebee UI','preferred_app', str(list(self.configured_set))) - with open(Config.ui_config_file_path,'w') as file_object: self.ui_config.write(file_object) + def get_configured_from_check (self): + for app in list(self.local_set): + desktop_file = DesktopFile(app) + if desktop_file.is_configured(): + yield [ desktop_file.get_app_info()[0], desktop_file.get_exec_list() ] - def get_apps_info (self): - for file_name in self.local_set: - desktop_file = DesktopFile(file_name, local=True) - app_info_list = desktop_file.get_app_info() - app_config = desktop_file.get_app_config() - if app_config[0]==True : self.configured_set.add(file_name) - yield app_info_list + [True] + app_config - self.store_configured() - for file_name in self.global_set: - app_info_list = DesktopFile(file_name, local=False).get_app_info() - yield app_info_list + [True] + 4*[False] + ['default'] - + def get_apps_info (self): + for file_name in self.local_set: + desktop_file = DesktopFile(file_name, local=True) + app_info_list = desktop_file.get_app_info() + app_config = desktop_file.get_app_config() + yield app_info_list + [True] + app_config + for file_name in self.global_set: + app_info_list = DesktopFile(file_name, local=False).get_app_info() + yield app_info_list + [True] + 4*[False] + ['default'] - def configure_file (self, file_name): - if file_name in self.local_set: - DesktopFile(file_name, local=True).add_shortcuts() #False : don't add the tag : created for bumblebee - print 'Bumblebee Shortcuts added to: ' + file_name - elif file_name in self.global_set: - DesktopFile(file_name, local=False).add_shortcuts() #True : add the tag : created for bumblebee - self.local_set.add(file_name) - self.global_set.remove(file_name) - print 'Bumblebee Shortcuts added to a desktop file created: ' + file_name - else : print "ERROR : The app name of configured file is not recognized" - self.configured_set.add(file_name) + def configure_file (self, file_name): + if file_name in self.local_set: + DesktopFile(file_name, local=True).configure_file() #False : don't add the tag : created for bumblebee + print 'Bumblebee Shortcuts added to: ' + file_name + elif file_name in self.global_set: + DesktopFile(file_name, local=False).configure_file() #True : add the tag : created for bumblebee + self.local_set.add(file_name) + self.global_set.remove(file_name) + print 'Bumblebee Shortcuts added to a desktop file created: ' + file_name + else : print "ERROR : The app name of configured file is not recognized" - def unconfigure_file (self, file_name): - if DesktopFile(file_name).unconfigure_file(): - self.local_set.remove(file_name) - self.global_set.add(file_name) - print 'Desktop file created for Bumblebee removed: ' + file_name - else: print 'Desktop file modified for Bumblebee is unconfigured: ' + file_name - self.configured_set.remove(file_name) + def unconfigure_file (self, file_name): + if DesktopFile(file_name).unconfigure_file(): + self.local_set.remove(file_name) + self.global_set.add(file_name) + print 'Desktop file created for Bumblebee removed: ' + file_name + else: print 'Desktop file modified for Bumblebee is unconfigured: ' + file_name #TODO : Rewrite in order to allow use of the script without user interface -#MAIN DESKTOP FILE CLASS +# MAIN DESKTOP FILE CLASS class DesktopFile: -###INITIALIZATION OF A DESKTOP FILE OBJECT - def __init__(self,file_name,local=True): - """Function that create a desktop file object that can be parsed""" - self.file_name_without_extension=file_name - self.file_name_with_extension=self.file_name_without_extension + ".desktop" - self.local = local - if self.local == True : - self.file_path = Config.user_desktop_file_directory + self.file_name_with_extension - elif self.local == False : self.file_path = Config.global_desktop_file_directory + self.file_name_with_extension - self.config = ConfigParser.ConfigParser() - self.config.optionxform = str - self.config.read(self.file_path) +# INITIALIZATION OF A DESKTOP FILE OBJECT + def __init__(self,file_name,local=True): + """Function that create a desktop file object that can be parsed""" + self.file_name_without_extension=file_name + self.file_name_with_extension=self.file_name_without_extension + ".desktop" + self.local = local + if self.local == True : + self.file_path = Config.user_desktop_file_directory + self.file_name_with_extension + elif self.local == False : self.file_path = Config.global_desktop_file_directory + self.file_name_with_extension + self.config = ConfigParser.ConfigParser() + self.config.optionxform = str + self.config.read(self.file_path) -###FUNCTIONS TO GET THE VALUES INSIDE ALL DESKTOP FILES - def get_app_info(self): - """Function to get values inside a desktop file object : - Application Name, File Name, Application Categories List, Application Icon Path""" - return [self.config.get('Desktop Entry','Name'), self.file_name_without_extension, self.get_category(), self.get_icon_path()] +# FUNCTIONS TO GET THE VALUES INSIDE ALL DESKTOP FILES + def get_app_info(self): + """Function to get values inside a desktop file object : + Application Name, File Name, Application Categories List, Application Icon Path""" + return [self.config.get('Desktop Entry','Name'), self.file_name_without_extension, self.get_category(), self.get_icon_path()] - def get_category(self, app_category=Config.unmatch_categorie[0]): - """Function to get the main category if categories are defined in the desktop file""" - try: - for item in reversed(self.config.get('Desktop Entry','Categories').split(';')): - for matching_item in reversed(Config.categorie_list): - if item==matching_item[0] : app_category = str(item) - except ConfigParser.NoOptionError: app_category=Config.uncategorized_categorie[0] - finally: return app_category + def get_category(self, app_category=Config.unmatch_categorie[0]): + """Function to get the main category if categories are defined in the desktop file""" + try: + for item in reversed(self.config.get('Desktop Entry','Categories').split(';')): + for matching_item in reversed(Config.categorie_list): + if item==matching_item[0] : app_category = str(item) + except ConfigParser.NoOptionError: app_category=Config.uncategorized_categorie[0] + finally: return app_category - def get_icon_path(self): - """Function to get the icon path or the icon name if an icon is defined in the desktop file""" - try : - icon_name=self.config.get('Desktop Entry', 'Icon') - if os.path.exists(icon_name): return icon_name - else: - try : return os.path.splitext(os.path.basename(icon_name))[0] - except : return Config.default_icon_name - except ConfigParser.NoOptionError: - return Config.default_icon_name - - - def is_configured(self): - """Function to check if the desktop file is configured for Bumblebee or not""" - try: - if (self.config.has_section('BumblebeeDisable Shortcut Group') - and self.config.has_section('BumblebeeEnable Shortcut Group') - and self.config.has_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts')): return True - else: return False - except: return False + def get_icon_path(self): + """Function to get the icon path or the icon name if an icon is defined in the desktop file""" + try : + icon_name=self.config.get('Desktop Entry', 'Icon') + if os.path.exists(icon_name): return icon_name + else: + try : return os.path.splitext(os.path.basename(icon_name))[0] + except : return Config.default_icon_name + except ConfigParser.NoOptionError: + return Config.default_icon_name + + def is_configured(self): + """Function to check if the desktop file is configured for Bumblebee or not""" + try: + if (self.config.has_section('BumblebeeDisable Shortcut Group') + and self.config.has_section('BumblebeeEnable Shortcut Group') + and self.config.has_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts')): return True + else: return False + except: return False -###FUNCTIONS TO GET VALUE INSIDE LOCAL DESKTOP FILES - def get_app_config(self): - """Function to search for configuration inside a local desktop file object : - Configured, (Selected by default : unselected), Mode, 32bits, Compression - """ - self.app_exec= self.config.get('Desktop Entry','Exec') - if self.is_configured(): return [True, True] + self.get_mode() - else: return [False, False] + [None] + self.get_exec_config(self.app_exec)[1:] - #else: return [False, False] + [None] + [False] + ['default'] - - - def get_mode(self): - """Function to get the mode and the exec config in a desktop file""" - Shortcuts = self.config.get('Desktop Entry', 'X-Ayatana-Desktop-Shortcuts') - exec_config= self.get_exec_config(self.app_exec) - if ( 'BumblebeeEnable' in Shortcuts and 'optirun ' in self.app_exec and exec_config[0] == True ): - return [Config.mode_keys['perf']] + exec_config[1:] - elif ( 'BumblebeeDisable' in Shortcuts and 'optirun ' in self.app_exec and exec_config[0] == False ) : - return [Config.mode_keys['eco']] + exec_config[1:] - elif ( 'BumblebeeDisable' in Shortcuts and not 'optirun ' in self.app_exec ): - return [Config.mode_keys['option']] + self.get_exec_config(self.config.get('BumblebeeDisable Shortcut Group','Exec'))[1:] - else: return ['Unrecognized mode'] + exec_config[1:] - - def set_true(arg, next_arg=None): return {arg:True} +# FUNCTIONS TO GET VALUE INSIDE LOCAL DESKTOP FILES + def get_app_config(self): + """Function to search for configuration inside a local desktop file object : + Configured, (Selected by default : unselected), Mode, 32bits, Compression + """ + self.app_exec= self.config.get('Desktop Entry','Exec') + if self.is_configured(): return [True, True] + self.get_mode() + else: return [False, False] + [None] + self.get_exec_config(self.app_exec)[1:] + #else: return [False, False] + [None] + [False] + ['default'] - def get_compression(arg, next_arg=None, default=Config.default_compression): - if (next_arg in Config.compression_list and next_arg != default): return {arg:next_arg} + def get_mode(self): + """Function to get the mode and the exec config in a desktop file""" + Shortcuts = self.config.get('Desktop Entry', 'X-Ayatana-Desktop-Shortcuts') + exec_config= self.get_exec_config(self.app_exec) + if ( 'BumblebeeEnable' in Shortcuts and 'optirun ' in self.app_exec and exec_config[0] == True ): + return [Config.mode_keys['perf']] + exec_config[1:] + elif ( 'BumblebeeDisable' in Shortcuts and 'optirun ' in self.app_exec and exec_config[0] == False ) : + return [Config.mode_keys['eco']] + exec_config[1:] + elif ( 'BumblebeeDisable' in Shortcuts and not 'optirun ' in self.app_exec ): + return [Config.mode_keys['option']] + self.get_exec_config(self.config.get('BumblebeeDisable Shortcut Group','Exec'))[1:] + else: return ['Unrecognized mode'] + exec_config[1:] + + def set_true(arg, next_arg=None): return {arg:True} - def get_exec_config(self, Exec, i=-1, - case={'-32':set_true, '-f':set_true, '-c':get_compression}, - skip=['optirun', 'optirun', '-d', ':0', ':1', ':2'] + Config.compression_list): - """Function to search for configuration inside optirun arguments in the desktop file object : - Force_eco, 32bits, Compression""" + def get_compression(arg, next_arg=None, default=Config.default_compression): + if (next_arg in Config.compression_list and next_arg != default): return {arg:next_arg} + def get_exec_config(self, Exec, i=-1, + case={'-32':set_true, '-f':set_true, '-c':get_compression}, + skip=['optirun', 'optirun', '-d', ':0', ':1', ':2'] + Config.compression_list): + """Function to search for configuration inside optirun arguments in the desktop file object : + Force_eco, 32bits, Compression""" arg_list=re.split(' ',Exec) - exec_config={'-f':False, '-32':False, '-c':'default'} - for arg in arg_list: - i = i+1 - if arg in case: exec_config.update(case.get(arg)(arg,next_arg=arg_list[i+1])) - elif arg in skip: continue - else: break - return [exec_config['-f']] + [exec_config['-32']] + [exec_config['-c']] + exec_config={'-f':False, '-32':False, '-c':'default'} + for arg in arg_list: + i = i+1 + if arg in case: exec_config.update(case.get(arg)(arg,next_arg=arg_list[i+1])) + elif arg in skip: continue + else: break + return [exec_config['-f']] + [exec_config['-32']] + [exec_config['-c']] -#FUNCTION TO GET THE LIST OF ARGUMENT FOR SUBPROCESS - def get_exec_list(self): - return re.split(' ',self.config.get('BumblebeeDisable Shortcut Group','Exec')) +# FUNCTION TO GET THE LIST OF ARGUMENT FOR SUBPROCESS + def get_exec_list(self): + return re.split(' ',self.config.get('BumblebeeDisable Shortcut Group','Exec')) - -###FUNCTIONS TO CONFIGURE THE FILES WITH SHORTCUTS - def write_config_to_file(self,output_file_name): - with open(output_file_name,'w') as file_object: - self.config.write(file_object) +# FUNCTIONS TO CONFIGURE THE FILES WITH SHORTCUTS + def write_config_to_file(self,output_file_name): + with open(output_file_name,'w') as file_object: + self.config.write(file_object) - def configure_file(self): - """Function to configure the local or global desktop file""" - if self.local == False: - try : self.config.set('Desktop Entry', 'Comment', self.config.get('Desktop Entry','Comment') + '(Bumblebee enabled)') - except ConfigParser.NoOptionError: - self.config.set('Desktop Entry', 'Comment', 'This file has been created for Bumblebee (Bumblebee enabled)') - self.add_shortcuts() - os.chmod(Config.user_desktop_file_directory + self.file_name_with_extension,0755) - elif self.local == True: - self.add_shortcuts + def configure_file(self): + """Function to configure the local or global desktop file""" + if self.local == False: + try : self.config.set('Desktop Entry', 'Comment', self.config.get('Desktop Entry','Comment') + ' (created for Bumblebee)') + except ConfigParser.NoOptionError: + self.config.set('Desktop Entry', 'Comment', 'This file has been created for Bumblebee.') + self.add_shortcuts() + os.chmod(Config.user_desktop_file_directory + self.file_name_with_extension,0755) + elif self.local == True: + self.add_shortcuts() - def add_shortcuts(self): - """Function to add shorcut section for bumblebee and add a shortcut to the desktop file object""" - self.prepend_option('Desktop Entry', 'X-Ayatana-Desktop-Shortcuts', 'BumblebeeDisable') - Exec = self.config.get('Desktop Entry', 'Exec') - #TODO Check if this is really needed - #self.config.set('Desktop Entry','OnlyShowIn','GNOME;Unity;") - self.add_shortcut_section('BumblebeeDisable Shortcut Group', 'Launch with Bumblebee', 'optirun -f ' + Exec) #Default setting is optional and forced - self.add_shortcut_section('BumblebeeEnable Shortcut Group', 'Launch without Bumblebee', Exec) - self.write_config_to_file(Config.user_desktop_file_directory + self.file_name_with_extension) - if self.local == False: os.chmod(Config.user_desktop_file_directory + self.file_name_with_extension,0755) + def add_shortcuts(self): + """Function to add shorcut section for bumblebee and add a shortcut to the desktop file object""" + self.prepend_option('Desktop Entry', 'X-Ayatana-Desktop-Shortcuts', 'BumblebeeDisable') + Exec = self.config.get('Desktop Entry', 'Exec') + #TODO Check if this is really needed + #self.config.set('Desktop Entry','OnlyShowIn','GNOME;Unity;") + self.add_shortcut_section('BumblebeeDisable Shortcut Group', 'Launch with Bumblebee', 'optirun ' + Exec) #Default setting is optional and forced + self.add_shortcut_section('BumblebeeEnable Shortcut Group', 'Launch without Bumblebee', Exec) + self.write_config_to_file(Config.user_desktop_file_directory + self.file_name_with_extension) + if self.local == False: os.chmod(Config.user_desktop_file_directory + self.file_name_with_extension,0755) - def add_shortcut_section(self,Section_title,Section_name,Section_exec): - self.config.add_section(Section_title) - self.config.set(Section_title, 'Name', Section_name) - self.config.set(Section_title, 'Exec', Section_exec) - self.config.set(Section_title, 'TargetEnvironment', 'Unity') + def add_shortcut_section(self,Section_title,Section_name,Section_exec): + self.config.add_section(Section_title) + self.config.set(Section_title, 'Name', Section_name) + self.config.set(Section_title, 'Exec', Section_exec) + self.config.set(Section_title, 'TargetEnvironment', 'Unity') - def prepend_option(self,section,option,value): - """Function to prepend a value to an option inside section of a desktop file object""" - if self.config.has_option(section,option) == True: self.config.set(section,option, value + ";" + self.config.get(section,option)) - else : self.config.set(section,option,value) + def prepend_option(self,section,option,value): + """Function to prepend a value to an option inside section of a desktop file object""" + if self.config.has_option(section,option) == True: self.config.set(section,option, value + ";" + self.config.get(section,option)) + else : self.config.set(section,option,value) -###FUNCTIONS TO UNCONFIGURE FILES OR REMOVE THEM - def is_created(self): - """Function to check if the file is tagged as created for Bumblebee or not""" - try: #FIXME Bumblebee Enable must not be set in comment but somewhere else - if 'Bumblebee enabled' in self.config.get('Desktop Entry','Comment'): return True - else : return False - except ConfigParser.NoOptionError: return False +# FUNCTIONS TO UNCONFIGURE FILES OR REMOVE THEM + def is_created(self): + """Function to check if the file is tagged as created for Bumblebee or not""" + try: #FIXME Bumblebee Enable must not be set in comment but somewhere else + if 'created for Bumblebee' in self.config.get('Desktop Entry','Comment'): return True + else : return False + except ConfigParser.NoOptionError: return False - def unconfigure_file(self): - """Function to unconfigure a file configured for Bumblebee : remove the shortcuts or remove the file if it's tagged as created for Bumblebee""" - if self.is_created(): - os.remove(self.file_path) - return True - else: - self.remove_shortcuts() - return False + def unconfigure_file(self): + """Function to unconfigure a file configured for Bumblebee : remove the shortcuts or remove the file if it's tagged as created for Bumblebee""" + if self.is_created(): + os.remove(self.file_path) + return True + else: + self.remove_shortcuts() + return False - def remove_shortcuts(self): - """Function to remove shorcut section for bumblebee and remove the shortcuts to the desktop file object""" - self.config.set('Desktop Entry','Exec',self.config.get('BumblebeeEnable Shortcut Group','Exec')) - Shortcuts=self.config.get('Desktop Entry','X-Ayatana-Desktop-Shortcuts') - if Shortcuts=='BumblebeeDisable' or Shortcuts=='BumblebeeEnable': self.config.remove_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts') - else : self.remove_prepend_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts','BumblebeeDisable\;|BumblebeeEnable\;') - self.config.remove_section('BumblebeeDisable Shortcut Group') - self.config.remove_section('BumblebeeEnable Shortcut Group') - self.write_config_to_file(self.file_path) + def remove_shortcuts(self): + """Function to remove shorcut section for bumblebee and remove the shortcuts to the desktop file object""" + self.config.set('Desktop Entry','Exec',self.config.get('BumblebeeEnable Shortcut Group','Exec')) + Shortcuts=self.config.get('Desktop Entry','X-Ayatana-Desktop-Shortcuts') + if Shortcuts=='BumblebeeDisable' or Shortcuts=='BumblebeeEnable': self.config.remove_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts') + else : self.remove_prepend_option('Desktop Entry','X-Ayatana-Desktop-Shortcuts','BumblebeeDisable\;|BumblebeeEnable\;') + self.config.remove_section('BumblebeeDisable Shortcut Group') + self.config.remove_section('BumblebeeEnable Shortcut Group') + self.write_config_to_file(self.file_path) - def remove_prepend_option(self,section,option,value): - """Function to remove a value from an option inside section of a desktop file object""" - if self.config.has_option(section,option) == True: self.config.set(section,option,re.sub(value,'',self.config.get(section,option))) + def remove_prepend_option(self,section,option,value): + """Function to remove a value from an option inside section of a desktop file object""" + if self.config.has_option(section,option) == True: self.config.set(section,option,re.sub(value,'',self.config.get(section,option))) -###FUNCTIONS TO CONFIGURE THE EXECUTION OF THE APPLICATION +# FUNCTIONS TO CONFIGURE THE EXECUTION OF THE APPLICATION - def set_exec_config(self, mode, bits32, compression): - """Function to set the option for optirun : default, 32 bits, on battery, compression""" - option='' - if bits32==True: option+='-32 ' - if not (compression == "default" or compression == Config.default_compression) : option+='-c '+ compression + ' ' - clean_exec= self.config.get('BumblebeeEnable Shortcut Group','Exec') - self.config.set('BumblebeeDisable Shortcut Group','Exec','optirun -f ' + option + clean_exec) - if mode == Config.mode_keys['perf']: - self.set_exec_config_default('optirun -f ' + option + clean_exec, 'BumblebeeDisable', 'BumblebeeEnable') - elif mode == Config.mode_keys['eco']: - self.set_exec_config_default('optirun ' + option + clean_exec, 'BumblebeeEnable', 'BumblebeeDisable') - else: - self.set_exec_config_default(clean_exec, 'BumblebeeEnable', 'BumblebeeDisable') - self.write_config_to_file(self.file_path) + def set_exec_config(self, mode, bits32, compression): + """Function to set the option for optirun : default, 32 bits, on battery, compression""" + option='' + if bits32==True: option+='-32 ' + if not (compression == "default" or compression == Config.default_compression) : option+='-c '+ compression + ' ' + clean_exec= self.config.get('BumblebeeEnable Shortcut Group','Exec') + self.config.set('BumblebeeDisable Shortcut Group','Exec','optirun ' + option + clean_exec) + if mode == Config.mode_keys['perf']: + self.set_exec_config_default('optirun ' + option + clean_exec, 'BumblebeeDisable', 'BumblebeeEnable') + elif mode == Config.mode_keys['eco']: + self.set_exec_config_default('optirun ' + option + clean_exec, 'BumblebeeEnable', 'BumblebeeDisable') + else: + self.set_exec_config_default(clean_exec, 'BumblebeeEnable', 'BumblebeeDisable') + self.write_config_to_file(self.file_path) - def set_exec_config_default(self,Exec,Initial_shortcut,Final_shortcut): - self.config.set('Desktop Entry','Exec',Exec) - self.config.set('Desktop Entry','X-Ayatana-Desktop-Shortcuts', re.sub(Initial_shortcut,Final_shortcut,self.config.get('Desktop Entry','X-Ayatana-Desktop-Shortcuts'))) + def set_exec_config_default(self,Exec,Initial_shortcut,Final_shortcut): + self.config.set('Desktop Entry','Exec',Exec) + self.config.set('Desktop Entry','X-Ayatana-Desktop-Shortcuts', re.sub(Initial_shortcut,Final_shortcut,self.config.get('Desktop Entry','X-Ayatana-Desktop-Shortcuts'))) if __name__=="__main__" : - print "DesktopFile.py can't run as a standalone application" - quit() + print "DesktopFile.py can't run as a standalone application" + quit() diff --git a/app/UNINSTALL b/app/UNINSTALL new file mode 100755 index 0000000..fae3867 --- /dev/null +++ b/app/UNINSTALL @@ -0,0 +1,31 @@ +### BEGIN LICENSE +# +# This file is part of bumblebee-ui. +# +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . +# +### END LICENSE + +rm /usr/share/icons/bumblebee* +rm -R /usr/share/bumblebee-ui/ +rm /usr/share/applications/bumblebee-app-settings.desktop +rm /usr/share/applications/bumblebee-indicator.desktop +rm /usr/local/bin/bumblebee-app-settings +rm /usr/local/bin/bumblebee-indicator + +#TO DO MANUALLY +#remove bumblebee-indicator at startup +# +# +# diff --git a/app/__init__.py b/app/__init__.py index 086b8e3..1d60a65 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,29 +1,21 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- ### BEGIN LICENSE # -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- +# This file is part of bumblebee-ui. # - -# This file is part of bumblebee-ui. -# -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# bumblebee-ui is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. # -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# bumblebee-ui is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. # -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . +# You should have received a copy of the GNU General Public License +# along with bumblebee-ui. If not, see . # ### END LICENSE diff --git a/bumblebee-indicator b/bumblebee-indicator deleted file mode 100755 index 0e3dfa0..0000000 --- a/bumblebee-indicator +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -### BEGIN LICENSE -# -# ---------------------------------------------------------------------------- -# "THE BEER-WARE LICENSE" (Revision 42): -# wrote this file. As long as you retain this notice you -# can do whatever you want with this stuff. If we meet some day, and you think -# this stuff is worth it, you can buy me a beer in return Davy Renaud (glyptostroboides) -# ---------------------------------------------------------------------------- -# - -# This file is part of bumblebee-ui. -# -# bumblebee-ui is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# bumblebee-ui is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with bumblebee-ui. If not, see . -# -### END LICENSE - -#UI MODULE -import pygtk -pygtk.require('2.0') -import gtk -import gobject -import appindicator -import pynotify - -#SYSTEM MODULE -import os -import subprocess - -###ORIGINAL CLASS -import app.Config -from app.AppSettings import Applications_settings, IconSet -from app.DesktopFile import DesktopFile, DesktopFileSet - -class BumblebeeIndicator(): -#TODO There must be a better way to get the icon than the URI -#FIXME The notification must be replaced when still visible - def notify_state(self, title, msg, icon_name): - self.notification= pynotify.Notification(title, msg, IconSet().get_uri(icon_name,48)) - self.notification.set_urgency(pynotify.URGENCY_CRITICAL) - #self.notification.set_timeout(pynotify.EXPIRES_NEVER) ###### NICE TO SHOW A MESSAGE SIMPLY - #FIXME The notification is to slow and this doesn't work - #self.notification.set_timeout(1000) - self.notification.show() - -###INITIALIZATION OF INDICATOR AND MENU - def __init__(self): - #TODO Choose the best category for the indicator : appindicator.CATEGORY_APPLICATION_STATUS or appindicator.CATEGORY_SYSTEM_SERVICES - self.ind = appindicator.Indicator ("bumblebee-indicator", "bumblebee-indicator", appindicator.CATEGORY_HARDWARE) - self.ind.set_status (appindicator.STATUS_ACTIVE) - self.ind.set_icon_theme_path(app.Config.icon_file_directory) - #TODO The icons style and accesibility must be enhanced : see icons/test directory - self.ind.set_icon(app.Config.icon_file_directory + "bumblebee-indicator.svg",'Bumblebee is unactive') - self.ind.set_attention_icon (app.Config.icon_file_directory + "bumblebee-indicator-active.svg",'Bumblebee is active') - - self.card_state=False - - self.build_menu() - - self.menu.show() - self.ind.set_menu(self.menu) - - def quit(self, widget, data=None): - gtk.main_quit() - - def build_menu(self): - self.menu = gtk.Menu() - self.switch = gtk.CheckMenuItem() - self.initial_state_checker() - self.switch.set_sensitive(False) - self.menu.append(self.switch) - - self.build_menu_separator() - - self.prefered_app_submenu = gtk.MenuItem("Preferred Apps") - self.update_menu() - self.prefered_app_submenu.connect('activate', self.update_menu) - self.menu.append(self.prefered_app_submenu) - - item2 = gtk.MenuItem("Configure Apps") - item2.connect("activate", self.app_configure) - self.menu.append(item2) - - #TODO An UI to configure Bumblebee would be nice - - #item3 = gtk.MenuItem("Configure Bumblebee") - #item3.show() - #self.menu.append(item3) - - self.build_menu_separator() - - quit = gtk.ImageMenuItem(gtk.STOCK_QUIT) - quit.connect("activate", self.quit) - self.menu.append(quit) - - self.menu.show_all() - - def build_menu_separator(self): - separator = gtk.SeparatorMenuItem() - separator.show() - self.menu.append(separator) - -#FUNCTION TO GET THE MENU FROM THE CONFIG FILE - def update_menu(self, widget=None): - self.pref_menu=gtk.Menu() - subitem = gtk.MenuItem("Glxgears") - subitem.connect("activate", self.call_app, ['optirun', 'glxgears']) - subitem.show() - self.pref_menu.append(subitem) - file_set.get_configured_from_cfg() - for file_name in file_set.configured_set: - [Name, File_name, Categorie, Icon_name] = DesktopFile(file_name).get_app_info() - subitem = gtk.MenuItem(label=Name) - #TODO : Discuss about style : is an icon needed for this simple shortcut - #subitem = gtk.ImageMenuItem() - #subitem.set_image(gtk.image_new_from_pixbuf(IconSet().get_pixbuf(Icon_name,icon_size=gtk.ICON_SIZE_MENU))) - #subitem.set_label(Name) - subitem.connect("activate", self.call_app, DesktopFile(file_name).get_exec_list()) - subitem.show() - self.pref_menu.append(subitem) - self.pref_menu.show() - self.prefered_app_submenu.set_submenu(self.pref_menu) - - - def initial_state_checker(self): - if self.attention_state_condition(): self.set_attention_state(notify=False) - else : self.set_active_state(notify=False) - - def state_checker(self): - if self.attention_state_condition(): - if self.card_state == False : self.set_attention_state() - elif self.card_state == True: self.set_active_state() - return True - - def attention_state_condition(self): - stdout_handle = os.popen("cat /etc/default/bumblebee |grep VGL_DISPLAY |cut -f2 -d:", "r") - vgl_display = stdout_handle.read().rstrip("\n") - vgl_display = "/tmp/.X%s-lock" % vgl_display - if os.path.exists(vgl_display): return True - else: return False - - def set_attention_state(self, notify=True): - self.ind.set_status(appindicator.STATUS_ATTENTION) - self.card_state = True - if notify == True: self.notify_state("Discrete card : ON", "Discrete graphic card is powered on", "bumblebee-indicator-active") - self.switch.set_label("Discrete card : ON") - self.switch.set_active(True) - - def set_active_state(self, notify=True): - self.ind.set_status(appindicator.STATUS_ACTIVE) - self.card_state = False - if notify == True: self.notify_state("Discrete card : OFF", "Discrete graphic card is powered off", "bumblebee-indicator") - self.switch.set_label("Discrete card : OFF") - self.switch.set_active(False) - - def app_configure(self,widget): - Applications_settings(file_set=file_set) - - def call_app(self, widget, app_exec): - #FIXME There is a problem when closing the launched app and when the indicator has been closed: the indicator is still running : What a daemon!! - subprocess.Popen(app_exec,shell=False) - - def about_box(self, widget, data=None): - dialog = gtk.MessageDialog(None, type=gtk.MESSAGE_INFO,buttons=gtk.BUTTONS_NONE,message_format="Preferences/About") - dialog.format_secondary_text("This is Optimus by mrs_sheep. \n It is based on bumblebee by MrMEEE. \n The configuration can (by now) only be set thorugh the config file!") - dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) - dialog.run() - dialog.destroy() - - - def main(self): - self.state_checker() - #FIXME It would be nice to avoid this loop : Maybe by using a signal emitted by the system - #gtk.timeout_add(1000,self.state_checker) - gobject.timeout_add_seconds(2, self.state_checker) - gtk.main() - -if __name__ == "__main__": - - file_set=DesktopFileSet() - indicator = BumblebeeIndicator() - indicator.main() -