Fix subprocess call from within Flatpak

Fixes #103
This commit is contained in:
Romain Failliot 2021-11-16 22:42:29 -05:00
parent 64be61d6b1
commit be40411b66
10 changed files with 163 additions and 123 deletions

View File

@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
### Fixed
- Fixed #103: the flatpak app can now call binaries on the host, such as `git`,
`svn`, etc. (PR #105)
## [0.7.0] - 2021-11-16

View File

@ -2,4 +2,4 @@
#
# Copyright (C) 2006-2009 Derrick Moser <derrick_moser@yahoo.com>
import @PKGDATADIR@/syntax/*.syntax
import @pkgdatadir@/syntax/*.syntax

View File

@ -33,7 +33,7 @@ endif
# Diffuse config file
conf = configuration_data()
conf.set('PKGDATADIR', pkgdatadir)
conf.set('pkgdatadir', pkgdatadir)
configure_file(
input: 'diffuserc.in',

View File

@ -8,10 +8,13 @@ finish-args:
- --socket=fallback-x11
- --share=ipc
- --filesystem=home
- --talk-name=org.freedesktop.Flatpak
modules:
- name: diffuse
builddir: true
buildsystem: meson
config-opts:
- -Duse_flatpak=true
sources:
- type: dir
path: .

1
meson_options.txt Normal file
View File

@ -0,0 +1 @@
option('use_flatpak', type: 'boolean', value: false)

27
src/constants.py.in Normal file
View File

@ -0,0 +1,27 @@
# Diffuse: a graphical tool for merging and comparing text files.
#
# Copyright (C) 2019 Derrick Moser <derrick_moser@yahoo.com>
# Copyright (C) 2021 Romain Failliot <romain.failliot@foolstep.com>
#
# This program 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 2 of the License, or
# (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
APP_NAME = 'Diffuse'
VERSION = '@VERSION@'
COPYRIGHT = '''{copyright} © 2006-2019 Derrick Moser
{copyright} © 2015-2021 Romain Failliot'''.format(copyright=_("Copyright"))
WEBSITE = 'https://mightycreak.github.io/diffuse/'
sysconfigdir = '@sysconfigdir@'
use_flatpak = @use_flatpak@

View File

@ -23,10 +23,8 @@ import sys
import gettext
import locale
VERSION = '@VERSION@'
pkgdatadir = '@pkgdatadir@'
localedir = '@localedir@'
sysconfigdir = '@sysconfigdir@'
sys.path.insert(1, pkgdatadir)
gettext.install('diffuse', localedir)
@ -34,4 +32,4 @@ gettext.install('diffuse', localedir)
if __name__ == '__main__':
from diffuse import main
sys.exit(main.main(VERSION, sysconfigdir))
sys.exit(main.main())

View File

@ -26,7 +26,6 @@ import glob
import re
import shlex
import stat
import subprocess
import unicodedata
import webbrowser
@ -53,6 +52,7 @@ from gi.repository import PangoCairo
from urllib.parse import urlparse
from diffuse import utils
from diffuse import constants
if not hasattr(__builtins__, 'WindowsError'):
# define 'WindowsError' so 'except' statements will work on all platforms
@ -739,7 +739,7 @@ class Preferences:
[ 'List',
[ 'Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16 ],
[ 'Boolean', 'tabs_always_show', False, _('Always show the tab bar') ],
[ 'Boolean', 'tabs_warn_before_quit', True, _('Warn me when closing a tab will quit %s') % utils.APP_NAME ]
[ 'Boolean', 'tabs_warn_before_quit', True, _('Warn me when closing a tab will quit %s') % constants.APP_NAME ]
],
_('Regional Settings'),
[ 'List',
@ -900,7 +900,7 @@ class Preferences:
ss.append(f'{k} "{v_escaped}"\n')
ss.sort()
f = open(self.path, 'w')
f.write(f'# This prefs file was generated by {utils.APP_NAME} {utils.VERSION}.\n\n')
f.write(f'# This prefs file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n')
for s in ss:
f.write(s)
f.close()
@ -1225,66 +1225,6 @@ def safeRelativePath(abspath1, name, prefs, cygwin_pref):
def bashEscape(s):
return "'" + s.replace("'", "'\\''") + "'"
# use popen to read the output of a command
def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
if success_results is None:
success_results = [ 0 ]
if utils.isWindows() and prefs.getBool(bash_pref):
# launch the command from a bash shell is requested
cmd = [ prefs.convertToNativePath('/bin/bash.exe'), '-l', '-c', 'cd {}; {}'.format(bashEscape(dn), ' '.join([ bashEscape(arg) for arg in cmd ])) ]
dn = None
# use subprocess.Popen to retrieve the file contents
if utils.isWindows():
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = subprocess.SW_HIDE
else:
info = None
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dn, startupinfo=info)
proc.stdin.close()
proc.stderr.close()
fd = proc.stdout
# read the command's output
s = fd.read()
fd.close()
if proc.wait() not in success_results:
raise IOError('Command failed.')
return s
# use popen to read the output of a command
def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None):
return strip_eols(splitlines(popenRead(dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore')))
# simulate use of popen with xargs to read the output of a command
def popenXArgsReadLines(dn, cmd, args, prefs, bash_pref):
# os.sysconf() is only available on Unix
if hasattr(os, 'sysconf'):
maxsize = os.sysconf('SC_ARG_MAX')
maxsize -= sum([ len(k) + len(v) + 2 for k, v in os.environ.items() ])
else:
# assume the Window's limit to CreateProcess()
maxsize = 32767
maxsize -= sum([ len(k) + 1 for k in cmd ])
ss = []
i, s, a = 0, 0, []
while i < len(args):
f = (len(a) == 0)
if f:
# start a new command line
a = cmd[:]
elif s + len(args[i]) + 1 <= maxsize:
f = True
if f:
# append another argument to the current command line
a.append(args[i])
s += len(args[i]) + 1
i += 1
if i == len(args) or not f:
ss.extend(popenReadLines(dn, a, prefs, bash_pref))
s, a = 0, []
return ss
# utility class to help support Git and Monotone
# represents a set of files and folders of interest for "git status" or
# "mtn automate inventory"
@ -1361,7 +1301,7 @@ class _Bzr:
isabs |= os.path.isabs(name)
args.append(safeRelativePath(self.root, name, prefs, 'bzr_cygwin'))
# run command
ss = popenReadLines(self.root, args, prefs, 'bzr_bash')
ss = utils.popenReadLines(self.root, args, prefs, 'bzr_bash')
# parse response
prev = 'before:' + rev
fs = _VcsFolderSet(names)
@ -1441,7 +1381,7 @@ class _Bzr:
prev = '-1'
fs = _VcsFolderSet(names)
added, modified, removed, renamed = {}, {}, {}, {}
for s in popenReadLines(self.root, args, prefs, 'bzr_bash'):
for s in utils.popenReadLines(self.root, args, prefs, 'bzr_bash'):
# parse response
if len(s) < 5:
continue
@ -1498,7 +1438,7 @@ class _Bzr:
return result
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('bzr_bin'), 'cat', '--name-from-revision', '-r', rev, safeRelativePath(self.root, name, prefs, 'bzr_cygwin') ], prefs, 'bzr_bash')
return utils.popenRead(self.root, [ prefs.getString('bzr_bin'), 'cat', '--name-from-revision', '-r', rev, safeRelativePath(self.root, name, prefs, 'bzr_cygwin') ], prefs, 'bzr_bash')
def _get_bzr_repo(path, prefs):
p = _find_parent_dir_with(path, '.bzr')
@ -1547,7 +1487,7 @@ class _Cvs:
prev = 'BASE'
fs = _VcsFolderSet(names)
modified = {}
for s in popenReadLines(self.root, args, prefs, 'cvs_bash'):
for s in utils.popenReadLines(self.root, args, prefs, 'cvs_bash'):
# parse response
if len(s) < 3 or s[0] not in 'ACMR':
continue
@ -1571,10 +1511,10 @@ class _Cvs:
def getRevision(self, prefs, name, rev):
if rev == 'BASE' and not os.path.exists(name):
# find revision for removed files
for s in popenReadLines(self.root, [ prefs.getString('cvs_bin'), 'status', safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash'):
for s in utils.popenReadLines(self.root, [ prefs.getString('cvs_bin'), 'status', safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash'):
if s.startswith(' Working revision:\t-'):
rev = s.split('\t')[1][1:]
return popenRead(self.root, [ prefs.getString('cvs_bin'), '-Q', 'update', '-p', '-r', rev, safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash')
return utils.popenRead(self.root, [ prefs.getString('cvs_bin'), '-Q', 'update', '-p', '-r', rev, safeRelativePath(self.root, name, prefs, 'cvs_cygwin') ], prefs, 'cvs_bash')
def _get_cvs_repo(path, prefs):
if os.path.isdir(os.path.join(path, 'CVS')):
@ -1608,7 +1548,7 @@ class _Darcs:
args.append(safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
# run command
# 'darcs whatsnew' will return 1 if there are no changes
ss = popenReadLines(self.root, args, prefs, 'darcs_bash', [0, 1])
ss = utils.popenReadLines(self.root, args, prefs, 'darcs_bash', [0, 1])
# parse response
i, n = 0, len(ss)
if mods:
@ -1701,7 +1641,7 @@ class _Darcs:
except ValueError:
args.extend([ '-h', rev ])
args.append(safeRelativePath(self.root, name, prefs, 'darcs_cygwin'))
return popenRead(self.root, args, prefs, 'darcs_bash')
return utils.popenRead(self.root, args, prefs, 'darcs_bash')
def _get_darcs_repo(path, prefs):
p = _find_parent_dir_with(path, '_darcs')
@ -1727,7 +1667,7 @@ class _Git:
prev = rev + '^'
fs = _VcsFolderSet(names)
modified = {}
for s in popenReadLines(self.root, args, prefs, 'git_bash'):
for s in utils.popenReadLines(self.root, args, prefs, 'git_bash'):
# parse response
if len(s) < 2 or s[0] not in 'ADM':
continue
@ -1762,7 +1702,7 @@ class _Git:
fs = _VcsFolderSet(names)
modified, renamed = {}, {}
# 'git status' will return 1 when a commit would fail
for s in popenReadLines(self.root, args, prefs, 'git_bash', [0, 1]):
for s in utils.popenReadLines(self.root, args, prefs, 'git_bash', [0, 1]):
# parse response
if len(s) < 3:
continue
@ -1828,13 +1768,13 @@ class _Git:
return result
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('git_bin'), 'show', '{}:{}'.format(rev, relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'git_bash')
return utils.popenRead(self.root, [ prefs.getString('git_bin'), 'show', '{}:{}'.format(rev, relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'git_bash')
def _get_git_repo(path, prefs):
if 'GIT_DIR' in os.environ:
try:
d = path
ss = popenReadLines(d, [ prefs.getString('git_bin'), 'rev-parse', '--show-prefix' ], prefs, 'git_bash')
ss = utils.popenReadLines(d, [ prefs.getString('git_bin'), 'rev-parse', '--show-prefix' ], prefs, 'git_bash')
if len(ss) > 0:
# be careful to handle trailing slashes
d = d.split(os.sep)
@ -1873,7 +1813,7 @@ class _Hg:
def _getPreviousRevision(self, prefs, rev):
if rev is None:
if self.working_rev is None:
ss = popenReadLines(self.root, [ prefs.getString('hg_bin'), 'id', '-i', '-t' ], prefs, 'hg_bash')
ss = utils.popenReadLines(self.root, [ prefs.getString('hg_bin'), 'id', '-i', '-t' ], prefs, 'hg_bash')
if len(ss) != 1:
raise IOError('Unknown working revision')
ss = ss[0].split(' ')
@ -1901,7 +1841,7 @@ class _Hg:
prev = self._getPreviousRevision(prefs, rev)
fs = _VcsFolderSet(names)
modified = {}
for s in popenReadLines(self.root, args, prefs, 'hg_bash'):
for s in utils.popenReadLines(self.root, args, prefs, 'hg_bash'):
# parse response
if len(s) < 3 or s[0] not in 'AMR':
continue
@ -1928,7 +1868,7 @@ class _Hg:
return self._getCommitTemplate(prefs, names, [ 'status', '-q' ], None)
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('hg_bin'), 'cat', '-r', rev, safeRelativePath(self.root, name, prefs, 'hg_cygwin') ], prefs, 'hg_bash')
return utils.popenRead(self.root, [ prefs.getString('hg_bin'), 'cat', '-r', rev, safeRelativePath(self.root, name, prefs, 'hg_cygwin') ], prefs, 'hg_bash')
def _get_hg_repo(path, prefs):
p = _find_parent_dir_with(path, '.hg')
@ -1947,7 +1887,7 @@ class _Mtn:
def getCommitTemplate(self, prefs, rev, names):
# build command
vcs_bin = prefs.getString('mtn_bin')
ss = popenReadLines(self.root, [ vcs_bin, 'automate', 'select', '-q', rev ], prefs, 'mtn_bash')
ss = utils.popenReadLines(self.root, [ vcs_bin, 'automate', 'select', '-q', rev ], prefs, 'mtn_bash')
if len(ss) != 1:
raise IOError('Ambiguous revision specifier')
args = [ vcs_bin, 'automate', 'get_revision', ss[0] ]
@ -1959,7 +1899,7 @@ class _Mtn:
# run command
prev = None
removed, added, modified, renamed = {}, {}, {}, {}
ss = popenReadLines(self.root, args, prefs, 'mtn_bash')
ss = utils.popenReadLines(self.root, args, prefs, 'mtn_bash')
i = 0
while i < len(ss):
# process results
@ -2002,7 +1942,7 @@ class _Mtn:
if removed or renamed:
# remove directories
removed_dirs = set()
for s in popenReadLines(self.root, [ vcs_bin, 'automate', 'get_manifest_of', prev ], prefs, 'mtn_bash'):
for s in utils.popenReadLines(self.root, [ vcs_bin, 'automate', 'get_manifest_of', prev ], prefs, 'mtn_bash'):
s = shlex.split(s)
if len(s) > 1 and s[0] == 'dir':
removed_dirs.add(s[1])
@ -2049,7 +1989,7 @@ class _Mtn:
isabs |= os.path.isabs(name)
# build list of interesting files
prev = 'h:'
ss = popenReadLines(self.root, args, prefs, 'mtn_bash')
ss = utils.popenReadLines(self.root, args, prefs, 'mtn_bash')
removed, added, modified, renamed = {}, {}, {}, {}
i = 0
while i < len(ss):
@ -2110,7 +2050,7 @@ class _Mtn:
return result
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('mtn_bin'), 'automate', 'get_file_of', '-q', '-r', rev, safeRelativePath(self.root, name, prefs, 'mtn_cygwin') ], prefs, 'mtn_bash')
return utils.popenRead(self.root, [ prefs.getString('mtn_bin'), 'automate', 'get_file_of', '-q', '-r', rev, safeRelativePath(self.root, name, prefs, 'mtn_cygwin') ], prefs, 'mtn_bash')
def _get_mtn_repo(path, prefs):
p = _find_parent_dir_with(path, '_MTN')
@ -2125,7 +2065,7 @@ class _Rcs:
def getFileTemplate(self, prefs, name):
args = [ prefs.getString('rcs_bin_rlog'), '-L', '-h', safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ]
rev = ''
for line in popenReadLines(self.root, args, prefs, 'rcs_bash'):
for line in utils.popenReadLines(self.root, args, prefs, 'rcs_bash'):
if line.startswith('head: '):
rev = line[6:]
return [ (name, rev), (name, None) ]
@ -2201,7 +2141,7 @@ class _Rcs:
args = [ safeRelativePath(self.root, k, prefs, 'rcs_cygwin') for k in r ]
# run command
r, k = {}, ''
for line in popenXArgsReadLines(self.root, cmd, args, prefs, 'rcs_bash'):
for line in utils.popenXArgsReadLines(self.root, cmd, args, prefs, 'rcs_bash'):
# parse response
if line.startswith('Working file: '):
k = prefs.convertToNativePath(line[14:])
@ -2214,7 +2154,7 @@ class _Rcs:
return [ [ (k, r[k]), (k, None) ] for k in sorted(r.keys()) ]
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('rcs_bin_co'), '-p', '-q', '-r' + rev, safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ], prefs, 'rcs_bash')
return utils.popenRead(self.root, [ prefs.getString('rcs_bin_co'), '-p', '-q', '-r' + rev, safeRelativePath(self.root, name, prefs, 'rcs_cygwin') ], prefs, 'rcs_bash')
def _get_rcs_repo(path, prefs):
if os.path.isdir(os.path.join(path, 'RCS')):
@ -2265,7 +2205,7 @@ class _Svn:
vcs, prefix = self._getVcs(), self._getURLPrefix()
n = len(prefix)
args = [ prefs.getString(vcs + '_bin'), 'info' ]
for s in popenReadLines(self.root, args, prefs, vcs + '_bash'):
for s in utils.popenReadLines(self.root, args, prefs, vcs + '_bash'):
if s.startswith(prefix):
self.url = s[n:]
break
@ -2312,7 +2252,7 @@ class _Svn:
# run command
fs = _VcsFolderSet(names)
modified, added, removed = {}, set(), set()
for s in popenReadLines(self.root, args, prefs, vcs_bash):
for s in utils.popenReadLines(self.root, args, prefs, vcs_bash):
status = self._parseStatusLine(s)
if status is None:
continue
@ -2368,7 +2308,7 @@ class _Svn:
# determine which are directories
added = {}
for p, v in m.items():
for s in popenReadLines(self.root, [ vcs_bin, 'list', '-r', rev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', rev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
if s in v:
# confirmed as added file
k = os.path.join(self.root, os.path.join(p, s))
@ -2394,7 +2334,7 @@ class _Svn:
m[d].add(b)
removed_dir, removed = set(), {}
for p, v in m.items():
for s in popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
if s.endswith('/'):
s = s[:-1]
if s in v:
@ -2412,7 +2352,7 @@ class _Svn:
tmp = removed_dir
removed_dir = set()
for p in tmp:
for s in popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
for s in utils.popenReadLines(self.root, [ vcs_bin, 'list', '-r', prev, '{}/{}'.format(self._getURL(prefs), p.replace(os.sep, '/')) ], prefs, vcs_bash):
if s.endswith('/'):
# confirmed item as directory
removed_dir.add(os.path.join(p, s[:-1]))
@ -2441,8 +2381,8 @@ class _Svn:
def getRevision(self, prefs, name, rev):
vcs_bin = prefs.getString('svn_bin')
if rev in [ 'BASE', 'COMMITTED', 'PREV' ]:
return popenRead(self.root, [ vcs_bin, 'cat', '{}@{}'.format(safeRelativePath(self.root, name, prefs, 'svn_cygwin'), rev) ], prefs, 'svn_bash')
return popenRead(self.root, [ vcs_bin, 'cat', '{}/{}@{}'.format(self._getURL(prefs), relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'), rev) ], prefs, 'svn_bash')
return utils.popenRead(self.root, [ vcs_bin, 'cat', '{}@{}'.format(safeRelativePath(self.root, name, prefs, 'svn_cygwin'), rev) ], prefs, 'svn_bash')
return utils.popenRead(self.root, [ vcs_bin, 'cat', '{}/{}@{}'.format(self._getURL(prefs), relpath(self.root, os.path.abspath(name)).replace(os.sep, '/'), rev) ], prefs, 'svn_bash')
def _get_svn_repo(path, prefs):
p = _find_parent_dir_with(path, '.svn')
@ -2472,7 +2412,7 @@ class _Svk(_Svn):
return str(int(rev) - 1)
def getRevision(self, prefs, name, rev):
return popenRead(self.root, [ prefs.getString('svk_bin'), 'cat', '-r', rev, '{}/{}'.format(self._getURL(prefs), relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'svk_bash')
return utils.popenRead(self.root, [ prefs.getString('svk_bin'), 'cat', '-r', rev, '{}/{}'.format(self._getURL(prefs), relpath(self.root, os.path.abspath(name)).replace(os.sep, '/')) ], prefs, 'svk_bash')
def _get_svk_repo(path, prefs):
name = path
@ -6548,7 +6488,7 @@ class SearchDialog(Gtk.Dialog):
# convenience method to request confirmation when closing the last tab
def confirmTabClose(parent):
dialog = utils.MessageDialog(parent, Gtk.MessageType.WARNING, _('Closing this tab will quit %s.') % (utils.APP_NAME, ))
dialog = utils.MessageDialog(parent, Gtk.MessageType.WARNING, _('Closing this tab will quit %s.') % (constants.APP_NAME, ))
end = (dialog.run() == Gtk.ResponseType.OK)
dialog.destroy()
return end
@ -6642,17 +6582,17 @@ class AboutDialog(Gtk.AboutDialog):
def __init__(self):
Gtk.AboutDialog.__init__(self)
self.set_logo_icon_name('io.github.mightycreak.Diffuse')
self.set_program_name(utils.APP_NAME)
self.set_version(utils.VERSION)
self.set_program_name(constants.APP_NAME)
self.set_version(constants.VERSION)
self.set_comments(_('Diffuse is a graphical tool for merging and comparing text files.'))
self.set_copyright(utils.COPYRIGHT)
self.set_website(utils.WEBSITE)
self.set_copyright(constants.COPYRIGHT)
self.set_website(constants.WEBSITE)
self.set_authors([ 'Derrick Moser <derrick_moser@yahoo.com>',
'Romain Failliot <romain.failliot@foolstep.com>' ])
self.set_translator_credits(_('translator-credits'))
license_text = [
utils.APP_NAME + ' ' + utils.VERSION + '\n\n',
utils.COPYRIGHT + '\n\n',
constants.APP_NAME + ' ' + constants.VERSION + '\n\n',
constants.COPYRIGHT + '\n\n',
_('''This program 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 2 of the License, or
@ -6898,7 +6838,7 @@ class Diffuse(Gtk.Window):
if self.headers[f].has_edits:
# warn users of any unsaved changes they might lose
dialog = Gtk.MessageDialog(self.get_toplevel(), Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.WARNING, Gtk.ButtonsType.NONE, _('Save changes before loading the new file?'))
dialog.set_title(utils.APP_NAME)
dialog.set_title(constants.APP_NAME)
dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
dialog.add_button(Gtk.STOCK_NO, Gtk.ResponseType.REJECT)
dialog.add_button(Gtk.STOCK_YES, Gtk.ResponseType.OK)
@ -7444,7 +7384,7 @@ class Diffuse(Gtk.Window):
menuspecs.append([ _('_Help'), [
[_('_Help Contents...'), self.help_contents_cb, None, Gtk.STOCK_HELP, 'help_contents'],
[],
[_('_About %s...') % (utils.APP_NAME, ), self.about_cb, None, Gtk.STOCK_ABOUT, 'about'] ] ])
[_('_About %s...') % (constants.APP_NAME, ), self.about_cb, None, Gtk.STOCK_ABOUT, 'about'] ] ])
# used to disable menu events when switching tabs
self.menu_update_depth = 0
@ -7561,7 +7501,7 @@ class Diffuse(Gtk.Window):
ss.append(f'{k} {v}\n')
ss.sort()
f = open(statepath, 'w')
f.write(f"# This state file was generated by {utils.APP_NAME} {utils.VERSION}.\n\n")
f.write(f"# This state file was generated by {constants.APP_NAME} {constants.VERSION}.\n\n")
for s in ss:
f.write(s)
f.close()
@ -7601,7 +7541,7 @@ class Diffuse(Gtk.Window):
buttons=Gtk.ButtonsType.NONE,
text=_('Some files have unsaved changes. Select the files to save before closing.'))
dialog.set_resizable(True)
dialog.set_title(utils.APP_NAME)
dialog.set_title(constants.APP_NAME)
# add list of files with unsaved changes
sw = Gtk.ScrolledWindow.new()
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
@ -7688,7 +7628,7 @@ class Diffuse(Gtk.Window):
# update window's title
def updateTitle(self, viewer):
title = self.notebook.get_tab_label(viewer).get_text()
self.set_title(f'{title} - {utils.APP_NAME}')
self.set_title(f'{title} - {constants.APP_NAME}')
# update the message in the status bar
def setStatus(self, s):
@ -8172,7 +8112,7 @@ class Diffuse(Gtk.Window):
del parts[-1]
if help_url is None:
# no local help file is available, show on-line help
help_url = utils.WEBSITE + 'manual.html'
help_url = constants.WEBSITE + 'manual.html'
# ask for localised manual
if utils.lang is not None:
help_url += '?lang=' + utils.lang
@ -8193,17 +8133,15 @@ GObject.signal_new('reload', Diffuse.FileDiffViewer.PaneHeader, GObject.SignalFl
GObject.signal_new('save', Diffuse.FileDiffViewer.PaneHeader, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ())
GObject.signal_new('save-as', Diffuse.FileDiffViewer.PaneHeader, GObject.SignalFlags.RUN_LAST, GObject.TYPE_NONE, ())
def main(version, sysconfigdir):
def main():
# app = Application()
# return app.run(sys.argv)
utils.VERSION = version
args = sys.argv
argc = len(args)
if argc == 2 and args[1] in [ '-v', '--version' ]:
print('%s %s\n%s' % (utils.APP_NAME, utils.VERSION, utils.COPYRIGHT))
print('%s %s\n%s' % (constants.APP_NAME, constants.VERSION, constants.COPYRIGHT))
sys.exit(0)
if argc == 2 and args[1] in [ '-h', '-?', '--help' ]:
print(_('''Usage:
@ -8270,7 +8208,7 @@ Display Options:
if utils.isWindows():
rc_file = os.path.join(utils.bin_dir, 'diffuserc')
else:
rc_file = os.path.join(utils.bin_dir, f'{sysconfigdir}/diffuserc')
rc_file = os.path.join(utils.bin_dir, f'{constants.sysconfigdir}/diffuserc')
for rc_file in rc_file, os.path.join(rc_dir, 'diffuserc'):
if os.path.isfile(rc_file):
rc_files.append(rc_file)

View File

@ -10,6 +10,7 @@ conf.set('VERSION', meson.project_version())
conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
conf.set('pkgdatadir', pkgdatadir)
conf.set('sysconfigdir', sysconfdir)
conf.set('use_flatpak', get_option('use_flatpak'))
configure_file(
input: 'diffuse.in',
@ -19,6 +20,14 @@ configure_file(
install_dir: get_option('bindir')
)
configure_file(
input: 'constants.py.in',
output: 'constants.py',
configuration: conf,
install: true,
install_dir: moduledir
)
diffuse_sources = [
'__init__.py',
'main.py',

View File

@ -20,12 +20,15 @@
import os
import sys
import locale
import subprocess
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from diffuse import constants
# convenience class for displaying a message dialogue
class MessageDialog(Gtk.MessageDialog):
def __init__(self, parent, type, s):
@ -61,6 +64,71 @@ def make_subdirs(p, ss):
pass
return p
def useFlatpak():
return constants.use_flatpak
# use popen to read the output of a command
def popenRead(dn, cmd, prefs, bash_pref, success_results=None):
if success_results is None:
success_results = [ 0 ]
if isWindows() and prefs.getBool(bash_pref):
# launch the command from a bash shell is requested
cmd = [ prefs.convertToNativePath('/bin/bash.exe'), '-l', '-c', 'cd {}; {}'.format(bashEscape(dn), ' '.join([ bashEscape(arg) for arg in cmd ])) ]
dn = None
# use subprocess.Popen to retrieve the file contents
if isWindows():
info = subprocess.STARTUPINFO()
info.dwFlags |= subprocess.STARTF_USESHOWWINDOW
info.wShowWindow = subprocess.SW_HIDE
else:
info = None
if useFlatpak():
cmd = [ 'flatpak-spawn', '--host' ] + cmd
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dn, startupinfo=info)
proc.stdin.close()
proc.stderr.close()
fd = proc.stdout
# read the command's output
s = fd.read()
fd.close()
if proc.wait() not in success_results:
raise IOError('Command failed.')
return s
# use popen to read the output of a command
def popenReadLines(dn, cmd, prefs, bash_pref, success_results=None):
return strip_eols(splitlines(popenRead(dn, cmd, prefs, bash_pref, success_results).decode('utf-8', errors='ignore')))
# simulate use of popen with xargs to read the output of a command
def popenXArgsReadLines(dn, cmd, args, prefs, bash_pref):
# os.sysconf() is only available on Unix
if hasattr(os, 'sysconf'):
maxsize = os.sysconf('SC_ARG_MAX')
maxsize -= sum([ len(k) + len(v) + 2 for k, v in os.environ.items() ])
else:
# assume the Window's limit to CreateProcess()
maxsize = 32767
maxsize -= sum([ len(k) + 1 for k in cmd ])
ss = []
i, s, a = 0, 0, []
while i < len(args):
f = (len(a) == 0)
if f:
# start a new command line
a = cmd[:]
elif s + len(args[i]) + 1 <= maxsize:
f = True
if f:
# append another argument to the current command line
a.append(args[i])
s += len(args[i]) + 1
i += 1
if i == len(args) or not f:
ss.extend(popenReadLines(dn, a, prefs, bash_pref))
s, a = 0, []
return ss
# use the program's location as a starting place to search for supporting files
# such as icon and help documentation
if hasattr(sys, 'frozen'):
@ -85,9 +153,3 @@ if isWindows():
else:
if lang is not None:
os.environ['LANG'] = lang
APP_NAME = 'Diffuse'
VERSION = '0.0.0'
COPYRIGHT = '''{copyright} © 2006-2019 Derrick Moser
{copyright} © 2015-2021 Romain Failliot'''.format(copyright=_("Copyright"))
WEBSITE = 'https://mightycreak.github.io/diffuse/'